Dotnet Csharp
Dotnet Csharp
Documentación de C#
Aprenda a escribir aplicaciones con el lenguaje de programación C# en la plataforma
.NET.
Aprender a programar en C#
b INTRODUCCIÓN
q VIDEO
g TUTORIAL
Tutoriales autoguiados
Tutorial en el explorador
i REFERENCIA
C# en Q&A
C# en Stack Overflow
C# en Discord
Aspectos básicos de C#
e INFORMACIÓN GENERAL
Un paseo por C#
Dentro de un programa de C#
Sistema de tipos
Técnicas funcionales
Excepciones
Estilo de codificación
g TUTORIAL
C# orientado a objetos
Conversión de tipos
Detección de patrones
Conceptos clave
e INFORMACIÓN GENERAL
Conceptos de programación
f INICIO RÁPIDO
Métodos
Propiedades
Indizadores
Iterators
Delegados
Events
p CONCEPTO
Control de versiones
Novedades
h NOVEDADES
Novedades de C# 11
Novedades de C# 10
Novedades de C# 9.0
Novedades de C# 8.0
g TUTORIAL
i REFERENCIA
Compatibilidad de versiones
i REFERENCIA
Palabras clave de C#
Operadores de C#
Mantente en contacto
i REFERENCIA
YouTube
Twitter
Paseo por el lenguaje C#
Artículo • 15/02/2023 • Tiempo de lectura: 16 minutos
C# resalta el control de versiones para garantizar que los programas y las bibliotecas
puedan evolucionar con el tiempo de manera compatible. Los aspectos del diseño de
C# afectados directamente por las consideraciones de versionamiento incluyen los
modificadores virtual y override independientes, las reglas para la resolución de
sobrecargas de métodos y la compatibilidad para declaraciones explícitas de miembros
de interfaz.
Arquitectura de .NET
Los programas de C# se ejecutan en .NET, un sistema de ejecución virtual denominado
Common Language Runtime (CLR) y un conjunto de bibliotecas de clases. CLR es la
implementación de Microsoft del estándar internacional Common Language
Infrastructure (CLI). CLI es la base para crear entornos de ejecución y desarrollo en los
que los lenguajes y las bibliotecas funcionan juntos sin problemas.
Hola a todos
El programa "Hola mundo" tradicionalmente se usa para presentar un lenguaje de
programación. En este caso, se usa C#:
C#
using System;
class Hello
Console.WriteLine("Hello, World");
El programa "Hola mundo" empieza con una directiva using que hace referencia al
espacio de nombres System . Los espacios de nombres proporcionan un método
jerárquico para organizar las bibliotecas y los programas de C#. Los espacios de
nombres contienen tipos y otros espacios de nombres; por ejemplo, el espacio de
nombres System contiene varios tipos, como la clase Console a la que se hace referencia
en el programa, y otros espacios de nombres, como IO y Collections . Una directiva
using que hace referencia a un espacio de nombres determinado permite el uso no
calificado de los tipos que son miembros de ese espacio de nombres. Debido a la
directiva using , puede utilizar el programa Console.WriteLine como abreviatura de
System.Console.WriteLine .
La clase Hello declarada por el programa "Hola mundo" tiene un miembro único, el
método llamado Main . El método Main se declara con el modificador static . Mientras
que los métodos de instancia pueden hacer referencia a una instancia de objeto
envolvente determinada utilizando la palabra clave this , los métodos estáticos
funcionan sin referencia a un objeto determinado. Por convención, un método estático
denominado Main sirve como punto de entrada de un programa de C#.
Tipos y variables
Un tipo define la estructura y el comportamiento de los datos en C#. La declaración de
un tipo puede incluir sus miembros, tipo base, interfaces que implementa y operaciones
permitidas para ese tipo. Una variable es una etiqueta que hace referencia a una
instancia de un tipo específico.
Hay dos clases de tipos en C#: tipos de valor y tipos de referencia. Las variables de tipos
de valor contienen directamente sus datos. Las variables de tipos de referencia
almacenan referencias a los datos, lo que se conoce como objetos. Con los tipos de
referencia, es posible que dos variables hagan referencia al mismo objeto y que, por
tanto, las operaciones en una variable afecten al objeto al que hace referencia la otra.
Con los tipos de valor, cada variable tiene su propia copia de los datos y no es posible
que las operaciones en una variable afecten a la otra (excepto para las variables de
parámetro ref y out ).
Tipos de valor
Tipos simples
Entero con signo: sbyte , short , int , long
Entero sin signo: byte , ushort , uint , ulong
Caracteres Unicode: char , que representa una unidad de código UTF-16
Punto flotante binario IEEE: float , double
Punto flotante decimal de alta precisión: decimal
Booleano: bool , que representa valores booleanos, valores que son true o
false
Tipos de enumeración
Tipos definidos por el usuario con el formato enum E {...} . Un tipo enum es
un tipo distinto con constantes con nombre. Cada tipo enum tiene un tipo
subyacente, que debe ser uno de los ocho tipos enteros. El conjunto de
valores de un tipo enum es igual que el conjunto de valores del tipo
subyacente.
Tipos de estructura
Tipos definidos por el usuario con el formato struct S {...}
Tipos de valores que aceptan valores NULL
Extensiones de todos los demás tipos de valor con un valor null
Tipos de valor de tupla
Tipos definidos por el usuario con el formato (T1, T2, ...)
Tipos de referencia
Tipos de clase
Clase base definitiva de todos los demás tipos: object
Cadenas Unicode: string , que representa una secuencia de unidades de
código UTF-16
Tipos definidos por el usuario con el formato class C {...}
Tipos de interfaz
Tipos definidos por el usuario con el formato interface I {...}
Tipos de matriz
Unidimensional, multidimensional y escalonada. Por ejemplo, int[] , int[,]
y int[][] .
Tipos delegados
Tipos definidos por el usuario con el formato delegate int D(...)
Los programas de C# utilizan declaraciones de tipos para crear nuevos tipos. Una
declaración de tipos especifica el nombre y los miembros del nuevo tipo. Seis de las
categorías de tipos de C# las define el usuario: tipos de clase, tipos de estructura, tipos
de interfaz, tipos de enumeración, tipos de delegado y tipos de valor de tupla. También
puede declarar tipos record , bien sean record struct o record class . Los tipos de
registro tienen miembros sintetizados por el compilador. Los registros se usan
principalmente para almacenar valores, con un comportamiento mínimo asociado.
A tipo class define una estructura de datos que contiene miembros de datos
(campos) y miembros de función (métodos, propiedades y otros). Los tipos de
clase admiten herencia única y polimorfismo, mecanismos por los que las clases
derivadas pueden extender y especializar clases base.
Un tipo struct es similar a un tipo de clase, por el hecho de que representa una
estructura con miembros de datos y miembros de función. Pero a diferencia de las
clases, las estructuras son tipos de valor y no suelen requerir la asignación del
montón. Los tipos de estructura no admiten la herencia especificada por el usuario
y todos se heredan implícitamente del tipo object .
Un tipo interface define un contrato como un conjunto con nombre de
miembros públicos. Un valor class o struct que implementa interface debe
proporcionar implementaciones de miembros de la interfaz. Un interface puede
heredar de varias interfaces base, y un class o struct pueden implementar varias
interfaces.
Un tipo delegate representa las referencias a métodos con una lista de parámetros
determinada y un tipo de valor devuelto. Los delegados permiten tratar métodos
como entidades que se puedan asignar a variables y se puedan pasar como
parámetros. Los delegados son análogos a los tipos de función proporcionados
por los lenguajes funcionales. También son similares al concepto de punteros de
función de otros lenguajes. A diferencia de los punteros de función, los delegados
están orientados a objetos y tienen seguridad de tipos.
Los tipos que aceptan valores NULL no requieren una definición independiente. Para
cada tipo T que no acepta valores NULL, existe un tipo T? que acepta valores NULL
correspondiente, que puede tener un valor adicional, null . Por ejemplo, int? es un tipo
que puede contener cualquier entero de 32 bits o el valor null y string? es un tipo
que puede contener cualquier string o el valor null .
El sistema de tipos de C# está unificado, de tal forma que un valor de cualquier tipo
puede tratarse como object . Todos los tipos de C# directa o indirectamente se derivan
del tipo de clase object , y object es la clase base definitiva de todos los tipos. Los
valores de tipos de referencia se tratan como objetos mediante la visualización de los
valores como tipo object . Los valores de tipos de valor se tratan como objetos
mediante la realización de operaciones de conversión boxing y operaciones de conversión
unboxing. En el ejemplo siguiente, un valor int se convierte en object y vuelve a int .
C#
int i = 123;
object o = i; // Boxing
El sistema de tipos unificado de C# significa que los tipos de valor se tratan como
referencias de object "a petición". Debido a la unificación, las bibliotecas de uso
general que usan el tipo object se pueden utilizar con todos los tipos que se derivan de
object , incluidos los tipos de referencia y los tipos de valor.
Hay varios tipos de variables en C#, entre otras, campos, elementos de matriz, variables
locales y parámetros. Las variables representan ubicaciones de almacenamiento. Cada
variable tiene un tipo que determina qué valores se pueden almacenar en ella, como se
muestra a continuación.
C#
namespace Acme.Collections;
Entry _top;
public T Pop()
if (_top == null)
T result = _top.Data;
_top = _top.Next;
return result;
class Entry
Next = next;
Data = data;
Una pila es una colección de tipo "el primero que entra es el último que sale" (FILO). Los
elementos nuevos se agregan a la parte superior de la pila. Cuando se quita un
elemento, se quita de la parte superior de la pila. En el ejemplo anterior se declara el
tipo Stack que define el almacenamiento y comportamiento de una pila. Puede declarar
una variable que haga referencia a una instancia del tipo Stack para usar esa
funcionalidad.
C#
class Example
Para compilar este programa, necesitaría hacer referencia al ensamblado que contiene la
clase de pila que se define en el ejemplo anterior.
Siguiente
Tipos y miembros de C#
Artículo • 10/02/2023 • Tiempo de lectura: 7 minutos
Clases y objetos
Las clases son los tipos más fundamentales de C#. Una clase es una estructura de datos
que combina estados (campos) y acciones (métodos y otros miembros de función) en
una sola unidad. Una clase proporciona una definición para instancias de la clase,
también conocidas como objetos. Las clases admiten herencia y polimorfismo,
mecanismos por los que las clases derivadas pueden extender y especializar clases base.
Las clases nuevas se crean mediante declaraciones de clase. Una declaración de clase
comienza con un encabezado. El encabezado especifica lo siguiente:
C#
Las instancias de clases se crean mediante el operador new , que asigna memoria para
una nueva instancia, invoca un constructor para inicializar la instancia y devuelve una
referencia a la instancia. Las instrucciones siguientes crean dos objetos Point y
almacenan las referencias en esos objetos en dos variables:
C#
Parámetros de tipo
Las clases genéricas definen parámetros de tipos. Los parámetros de tipo son una lista
de nombres de parámetros de tipo entre paréntesis angulares. Los parámetros de tipo
siguen el nombre de la clase. Los parámetros de tipo pueden usarse luego en el cuerpo
de las declaraciones de clase para definir a los miembros de la clase. En el ejemplo
siguiente, los parámetros de tipo de Pair son TFirst y TSecond :
C#
Un tipo de clase que se declara para tomar parámetros de tipo se conoce como tipo de
clase genérica. Los tipos de estructura, interfaz y delegado también pueden ser
genéricos.
Cuando se usa la clase genérica, se deben proporcionar argumentos de tipo
para cada uno de los parámetros de tipo:
C#
Clases base
Una declaración de clase puede especificar una clase base. Tras el nombre de clase y los
parámetros de tipo, agregue un signo de dos puntos y el nombre de la clase base.
Omitir una especificación de la clase base es igual que derivarla del tipo object . En el
ejemplo siguiente, la clase base de Point3D es Point . En el primer ejemplo, la clase base
de Point es object :
C#
Z = z;
Una clase hereda a los miembros de su clase base. La herencia significa que una clase
contiene implícitamente casi todos los miembros de su clase base. Una clase no hereda
la instancia, los constructores estáticos ni el finalizador. Una clase derivada puede
agregar nuevos miembros a aquellos de los que hereda, pero no puede quitar la
definición de un miembro heredado. En el ejemplo anterior, Point3D hereda los
miembros X y Y de Point , y cada instancia de Point3D contiene tres miembros: X , Y y
Z.
Existe una conversión implícita de un tipo de clase a cualquiera de sus tipos de clase
base. Una variable de un tipo de clase puede hacer referencia a una instancia de esa
clase o a una instancia de cualquier clase derivada. Por ejemplo, dadas las declaraciones
de clase anteriores, una variable de tipo Point puede hacer referencia a una instancia
de Point o Point3D :
C#
Estructuras
Las clases definen tipos que admiten la herencia y el polimorfismo. Permiten crear
comportamientos sofisticados basados en jerarquías de clases derivadas. Por el
contrario, los tipos struct son tipos más simples, cuyo propósito principal es almacenar
valores de datos. Dichos tipos struct no pueden declarar un tipo base; se derivan
implícitamente de System.ValueType. No se pueden derivar otros tipos de struct a
partir de un tipo de struct . Están sellados implícitamente.
C#
Interfaces
Una interfaz define un contrato que se puede implementar mediante clases y structs.
Una interfaz se define para declarar capacidades que se comparten entre tipos distintos.
Por ejemplo, la interfaz System.Collections.Generic.IEnumerable<T> define una manera
coherente de recorrer todos los elementos de una colección, como una matriz. Una
interfaz puede contener métodos, propiedades, eventos e indexadores. Normalmente,
una interfaz no proporciona implementaciones de los miembros que define, sino que
simplemente especifica los miembros que se deben proporcionar mediante clases o
estructuras que implementan la interfaz.
C#
interface IControl
void Paint();
Las clases y los structs pueden implementar varias interfaces. En el ejemplo siguiente, la
clase EditBox implementa IControl y IDataBound .
C#
interface IDataBound
Cuando una clase o un struct implementan una interfaz determinada, las instancias de
esa clase o struct se pueden convertir implícitamente a ese tipo de interfaz. Por ejemplo
C#
Enumeraciones
Un tipo de enumeración define un conjunto de valores constantes. En el elemento enum
siguiente se declaran constantes que definen diferentes verduras de raíz:
C#
HorseRadish,
Radish,
Turnip
También puede definir un elemento enum que se usará de forma combinada como
marcas. La declaración siguiente declara un conjunto de marcas para las cuatro
estaciones. Se puede aplicar cualquier combinación de estaciones, incluido un valor All
que incluya todas las estaciones:
C#
[Flags]
None = 0,
Summer = 1,
Autumn = 2,
Winter = 4,
Spring = 8,
C#
C#
optionalInt = 5;
Tuplas
C# admite tuplas, lo que proporciona una sintaxis concisa para agrupar varios
elementos de datos en una estructura de datos ligera. Puede crear una instancia de una
tupla declarando los tipos y los nombres de los miembros entre ( y ) , como se
muestra en el ejemplo siguiente:
C#
//Output:
Las tuplas proporcionan una alternativa para la estructura de datos con varios miembros
sin usar los bloques de creación que se describen en el siguiente artículo.
Anterior Siguiente
Bloques de creación de programas de
C#
Artículo • 18/01/2023 • Tiempo de lectura: 26 minutos
Los tipos descritos en el artículo anterior de esta serie sobre el recorrido por C# se crean
mediante estos bloques de creación:
Miembros
Los miembros de un class son estáticos o de instancia. Los miembros estáticos
pertenecen a clases y los miembros de instancia pertenecen a objetos (instancias de
clases).
En la lista siguiente se proporciona una visión general de los tipos de miembros que
puede contener una clase.
Accesibilidad
Cada miembro de una clase tiene asociada una accesibilidad, que controla las regiones
del texto del programa que pueden acceder al miembro. Existen seis formas de
accesibilidad posibles. A continuación se resumen los modificadores de acceso.
protected : El acceso está limitado a esta clase o a las clases derivadas de esta
clase.
internal : El acceso está limitado al ensamblado actual ( .exe o .dll ).
protected internal : El acceso está limitado a esta clase, las clases derivadas de la
misma o las clases que forman parte del mismo ensamblado.
private protected : El acceso está limitado a esta clase o a las clases derivadas de
este tipo que forman parte del mismo ensamblado.
Campos
Un campo es una variable que está asociada con una clase o a una instancia de una
clase.
En el ejemplo siguiente, cada instancia de la clase Color tiene una copia independiente
de los campos de instancia R , G y B , pero solo hay una copia de los campos estáticos
Black , White , Red , Green y Blue :
C#
public byte R;
public byte G;
public byte B;
R = r;
G = g;
B = b;
Como se muestra en el ejemplo anterior, los campos de solo lectura se puede declarar
con un modificador readonly . La asignación a un campo de solo lectura únicamente se
puede producir como parte de la declaración del campo o en un constructor de la
misma clase.
Métodos
Un método es un miembro que implementa un cálculo o una acción que puede realizar
un objeto o una clase. A los métodos estáticos se accede a través de la clase. A los
métodos de instancia se accede a través de instancias de la clase.
Los métodos pueden tener una lista de parámetros, los cuales representan valores o
referencias a variables que se pasan al método. Los métodos tienen un tipo de valor
devuelto, el cual especifica el tipo del valor calculado y devuelto por el método. El tipo
de valor devuelto de un método es void si no devuelve un valor.
Al igual que los tipos, los métodos también pueden tener un conjunto de parámetros de
tipo, para lo cuales se deben especificar argumentos de tipo cuando se llama al método.
A diferencia de los tipos, los argumentos de tipo a menudo se pueden deducir de los
argumentos de una llamada al método y no es necesario proporcionarlos
explícitamente.
Cuando el cuerpo del método es una expresión única, el método se puede definir con
un formato de expresión compacta, tal y como se muestra en el ejemplo siguiente:
C#
Parámetros
Los parámetros se usan para pasar valores o referencias a variables a métodos. Los
parámetros de un método obtienen sus valores reales de los argumentos que se
especifican cuando se invoca el método. Hay cuatro tipos de parámetros: parámetros de
valor, parámetros de referencia, parámetros de salida y matrices de parámetros.
C#
int temp = x;
x = y;
y = temp;
int i = 1, j = 2;
C#
static void Divide(int x, int y, out int quotient, out int remainder)
quotient = x / y;
remainder = x % y;
C#
// ...
C#
int x, y, z;
x = 3;
y = 4;
z = 5;
C#
int x = 3, y = 4, z = 5;
args[0] = x;
args[1] = y;
args[2] = z;
Console.WriteLine(s, args);
Un cuerpo del método puede declarar variables que son específicas de la invocación del
método. Estas variables se denominan variables locales. Una declaración de variable
local especifica un nombre de tipo, un nombre de variable y, posiblemente, un valor
inicial. En el ejemplo siguiente se declara una variable local i con un valor inicial de
cero y una variable local j sin ningún valor inicial.
C#
class Squares
int i = 0;
int j;
j = i * i;
i++;
C# requiere que se asigne definitivamente una variable local antes de que se pueda
obtener su valor. Por ejemplo, si la declaración de i anterior no incluyera un valor
inicial, el compilador notificaría un error con los usos posteriores de i porque i no se
asignaría definitivamente en esos puntos del programa.
Puede usar una instrucción return para devolver el control a su llamador. En un método
que devuelve void , las instrucciones return no pueden especificar una expresión. En un
método que devuelve valores distintos de void, las instrucciones return deben incluir
una expresión que calcula el valor devuelto.
Métodos estáticos y de instancia
Un método declarado con un modificador static es un método estático. Un método
estático no opera en una instancia específica y solo puede acceder directamente a
miembros estáticos.
C#
class Entity
int _serialNo;
public Entity()
_serialNo = s_nextSerialNo++;
return _serialNo;
return s_nextSerialNo;
s_nextSerialNo = value;
C#
Entity.SetNextSerialNo(1000);
Entity e1 = new();
Entity e2 = new();
C#
double _value;
_value = value;
return _value;
string _name;
_name = name;
return Convert.ToDouble(value);
Expression _left;
char _op;
Expression _right;
_left = left;
_op = op;
_right = right;
double x = _left.Evaluate(vars);
double y = _right.Evaluate(vars);
switch (_op)
Las cuatro clases anteriores se pueden usar para modelar expresiones aritméticas. Por
ejemplo, usando instancias de estas clases, la expresión x + 3 se puede representar de
la manera siguiente.
C#
new VariableReference("x"),
'+',
new Constant(3));
C#
new VariableReference("x"),
'*',
new Operation(
new VariableReference("y"),
'+',
new Constant(2)
)
);
vars["x"] = 3;
vars["y"] = 5;
Console.WriteLine(e.Evaluate(vars)); // "21"
vars["x"] = 1.5;
vars["y"] = 9;
Console.WriteLine(e.Evaluate(vars)); // "16.5"
Sobrecarga de métodos
La sobrecarga de métodos permite que varios métodos de la misma clase tengan el
mismo nombre mientras tengan signaturas únicas. Al compilar una invocación de un
método sobrecargado, el compilador usa la resolución de sobrecarga para determinar el
método concreto que de invocará. La resolución de sobrecarga busca el método que
mejor coincida con los argumentos. Si no se puede encontrar la mejor coincidencia, se
genera un error. En el ejemplo siguiente se muestra la resolución de sobrecarga en
vigor. El comentario para cada invocación del método UsageExample muestra qué
método se invoca.
C#
class OverloadingExample
C#
T[] _items;
int _count;
set
if (value != _items.Length)
_items = newItems;
set
if (!object.Equals(_items[index], value)) {
_items[index] = value;
OnChanged();
_items[_count] = item;
_count++;
OnChanged();
Changed?.Invoke(this, EventArgs.Empty);
return false;
if (!object.Equals(a._items[i], b._items[i]))
return false;
return true;
Equals(a, b);
!Equals(a, b);
Constructores
C# admite constructores de instancia y estáticos. Un constructor de instancia es un
miembro que implementa las acciones necesarias para inicializar una instancia de una
clase. Un constructor estático es un miembro que implementa las acciones necesarias
para inicializar una clase en sí misma cuando se carga por primera vez.
C#
Propiedades
Las propiedades son una extensión natural de los campos. Ambos son miembros con
nombre con tipos asociados y la sintaxis para acceder a los campos y las propiedades es
la misma. Pero a diferencia de los campos, las propiedades no denotan ubicaciones de
almacenamiento. Las propiedades tienen descriptores de acceso que especifican las
instrucciones ejecutadas cuando se leen o escriben sus valores. Un descriptor de acceso
get lee el valor. Un descriptor de acceso set escribe el valor.
Una propiedad se declara como un campo, salvo que la declaración finaliza con un
descriptor de acceso get o un descriptor de acceso set escrito entre los delimitadores {
y } en lugar de finalizar en un punto y coma. Una propiedad que tiene un descriptor de
acceso get y un descriptor de acceso set es una propiedad de lectura y escritura. Una
propiedad que solo tiene un descriptor de acceso get es una propiedad de solo lectura.
Una propiedad que solo tiene un descriptor de acceso set es una propiedad de solo
escritura.
La clase MyList<T> declara dos propiedades, Count y Capacity , que son de solo lectura
y de lectura y escritura, respectivamente. El código siguiente es un ejemplo de uso de
estas propiedades:
C#
Indexadores
Un indexador es un miembro que permite indexar de la misma manera que una matriz.
Un indexador se declara como una propiedad, excepto por el hecho que el nombre del
miembro es this , seguido por una lista de parámetros que se escriben entre los
delimitadores [ y ] . Los parámetros están disponibles en los descriptores de acceso del
indexador. De forma similar a las propiedades, los indexadores pueden ser lectura y
escritura, de solo lectura y de solo escritura, y los descriptores de acceso de un
indexador pueden ser virtuales.
C#
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
string s = names[i];
names[i] = s.ToUpper();
Los indizadores se pueden sobrecargar. Una clase puede declarar varios indexadores
siempre y cuando el número o los tipos de sus parámetros sean diferentes.
Eventos
Un evento es un miembro que permite que una clase u objeto proporcionen
notificaciones. Un evento se declara como un campo, excepto por el hecho de que la
declaración incluye una palabra clave event , y el tipo debe ser un tipo delegado.
Dentro de una clase que declara un miembro de evento, el evento se comporta como
un campo de un tipo delegado (siempre que el evento no sea abstracto y no declare
descriptores de acceso). El campo almacena una referencia a un delegado que
representa los controladores de eventos que se han agregado al evento. Si no existen
controladores de eventos, el campo es null .
C#
class EventExample
s_changeCount++;
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
Console.WriteLine(s_changeCount); // "3"
Operadores
Un operador es un miembro que define el significado de aplicar un operador de
expresión determinado a las instancias de una clase. Se pueden definir tres tipos de
operadores: operadores unarios, operadores binarios y operadores de conversión. Todos
los operadores se deben declarar como public y static .
C#
MyList<int> a = new();
a.Add(1);
a.Add(2);
MyList<int> b = new();
b.Add(1);
b.Add(2);
b.Add(3);
El primer objeto Console.WriteLine genera True porque las dos listas contienen el
mismo número de objetos con los mismos valores en el mismo orden. Si MyList<T> no
hubiera definido operator == , el primer objeto Console.WriteLine habría generado
False porque a y b hacen referencia a diferentes instancias de MyList<int> .
Finalizadores
Un finalizador es un miembro que implementa las acciones necesarias para finalizar una
instancia de una clase. Normalmente, se necesita un finalizador para liberar los recursos
no administrados. Los finalizadores no pueden tener parámetros, no pueden tener
modificadores de accesibilidad y no se pueden invocar de forma explícita. El finalizador
de una instancia se invoca automáticamente durante la recolección de elementos no
utilizados. Para obtener más información, vea el artículo sobre finalizadores.
El recolector de elementos no utilizados tiene una amplia libertad para decidir cuándo
debe recolectar objetos y ejecutar finalizadores. En concreto, los intervalos de las
invocaciones de finalizador no son deterministas y los finalizadores se pueden ejecutar
en cualquier subproceso. Por estas y otras razones, las clases deben implementar
finalizadores solo cuando no haya otras soluciones que sean factibles.
La instrucción using proporciona un mejor enfoque para la destrucción de objetos.
Expresiones
Las expresiones se construyen con operandos y operadores. Los operadores de una
expresión indican qué operaciones se aplican a los operandos. Ejemplos de operadores
incluyen + , - , * , / y new . Algunos ejemplos de operandos son literales, campos,
variables locales y expresiones.
Anterior Siguiente
Áreas de lenguaje principales de C#
Artículo • 18/01/2023 • Tiempo de lectura: 10 minutos
Matrices
Una matriz es una estructura de datos que contiene un número de variables a las que se
accede mediante índices calculados. Las variables contenidas en una matriz,
denominadas también elementos de la matriz, son todas del mismo tipo. Este tipo se
denomina tipo de elemento de la matriz.
Los tipos de matriz son tipos de referencia, y la declaración de una variable de matriz
simplemente establece un espacio reservado para una referencia a una instancia de
matriz. Las instancias de matriz reales se crean dinámicamente en tiempo de ejecución
mediante el operador new . La operación new especifica la longitud de la nueva instancia
de matriz, que luego se fija para la vigencia de la instancia. Los índices de los elementos
de una matriz van de 0 a Length - 1 . El operador new inicializa automáticamente los
elementos de una matriz a su valor predeterminado, que, por ejemplo, es cero para
todos los tipos numéricos y null para todos los tipos de referencias.
C#
int[] a = new int[10];
a[i] = i * i;
Console.WriteLine($"a[{i}] = {a[i]}");
Este ejemplo crea una matriz unidimensional y opera en ella. C# también admite
matrices multidimensionales. El número de dimensiones de un tipo de matriz, conocido
también como _clasificación del tipo de matriz, es uno más el número de comas entre
los corchetes del tipo de matriz. En el ejemplo siguiente se asignan una matriz
unidimensional, bidimensional y tridimensional, respectivamente.
C#
C#
La primera línea crea una matriz con tres elementos, cada uno de tipo int[] y cada uno
con un valor inicial de null . Las líneas siguientes inicializan entonces los tres elementos
con referencias a instancias de matriz individuales de longitud variable.
El operador new permite especificar los valores iniciales de los elementos de matriz
mediante un inicializador de matriz, que es una lista de las expresiones escritas entre
los delimitadores { y } . En el ejemplo siguiente se asigna e inicializa un tipo int[] con
tres elementos.
C#
C#
int[] a = { 1, 2, 3 };
C#
t[0] = 1;
t[1] = 2;
t[2] = 3;
int[] a = t;
C#
Console.WriteLine(item);
La instrucción foreach utiliza la interfaz IEnumerable<T>, por lo que puede trabajar con
cualquier colección.
Interpolación de cadenas
La interpolación de cadenas de C# le permite dar formato a las cadenas mediante la
definición de expresiones cuyos resultados se colocan en una cadena de formato. Por
ejemplo, en el ejemplo siguiente se imprime la temperatura de un día determinado a
partir de un conjunto de datos meteorológicos:
C#
Console.WriteLine($"The low and high temperature on {weatherData.Date:MM-dd-
yyyy}");
Detección de patrones
El lenguaje C# proporciona expresiones de coincidencia de patrones para consultar el
estado de un objeto y ejecutar código basado en dicho estado. Puede inspeccionar los
tipos y los valores de las propiedades y los campos para determinar qué acción se debe
realizar. También puede inspeccionar los elementos de una lista o matriz. La expresión
switch es la expresión primaria para la coincidencia de patrones.
C#
class Multiplier
double _factor;
class DelegateExample
return result;
Multiplier m = new(2.0);
Una instancia del tipo de delegado Function puede hacer referencia a cualquier método
que tome un argumento double y devuelva un valor double . El método Apply aplica un
elemento Function determinado a los elementos de double[] y devuelve double[] con
los resultados. En el método Main , Apply se usa para aplicar tres funciones diferentes a
un valor double[] .
Un delegado puede hacer referencia a una expresión lambda para crear una función
anónima (como (x) => x * x en el ejemplo anterior), un método estático (como
Math.Sin en el ejemplo anterior) o un método de instancia (como m.Multiply en el
ejemplo anterior). Un delegado que hace referencia a un método de instancia también
hace referencia a un objeto determinado y, cuando se invoca el método de instancia a
través del delegado, ese objeto se convierte en this en la invocación.
C#
Un delegado no conoce la clase del método al que hace referencia; de hecho, tampoco
tiene importancia. El método al que se hace referencia debe tener los mismos
parámetros y el mismo tipo de valor devuelto que el delegado.
async y await
C# admite programas asincrónicos con dos palabras clave: async y await . Puede
agregar el modificador async a una declaración de método para declarar que dicho
método es asincrónico. El operador await indica al compilador que espere de forma
asincrónica a que finalice un resultado. El control se devuelve al autor de la llamada, y el
método devuelve una estructura que administra el estado del trabajo asincrónico.
Normalmente, la estructura es un elemento System.Threading.Tasks.Task<TResult>, pero
puede ser cualquier tipo que admita el patrón awaiter. Estas características permiten
escribir código que se lee como su homólogo sincrónico, pero que se ejecuta de forma
asincrónica. Por ejemplo, el código siguiente descarga la página principal de
Microsoft Docs:
C#
Console.WriteLine($"{nameof(RetrieveDocsHomePage)}: Finished
downloading.");
return content.Length;
Atributos
Los tipos, los miembros y otras entidades en un programa de C # admiten
modificadores que controlan ciertos aspectos de su comportamiento. Por ejemplo, la
accesibilidad de un método se controla mediante los modificadores public , protected ,
internal y private . C # generaliza esta funcionalidad de manera que los tipos de
información declarativa definidos por el usuario se puedan adjuntar a las entidades del
programa y recuperarse en tiempo de ejecución. Los programas especifican esta
información declarativa mediante la definición y el uso de atributos.
C#
string _url;
string _topic;
Todas las clases de atributos se derivan de la clase base Attribute proporcionada por la
biblioteca .NET. Los atributos se pueden aplicar proporcionando su nombre, junto con
cualquier argumento, entre corchetes, justo antes de la declaración asociada. Si el
nombre de un atributo termina en Attribute , esa parte del nombre se puede omitir
cuando se hace referencia al atributo. Por ejemplo, HelpAttribute se puede usar de la
manera siguiente.
C#
[Help("https://docs.microsoft.com/dotnet/csharp/tour-of-csharp/features")]
[Help("https://docs.microsoft.com/dotnet/csharp/tour-of-
csharp/features",
Topic = "Display")]
public void Display(string text) { }
C#
object[] widgetClassAttributes =
widgetType.GetCustomAttributes(typeof(HelpAttribute), false);
if (widgetClassAttributes.Length > 0)
System.Reflection.MethodInfo displayMethod =
widgetType.GetMethod(nameof(Widget.Display));
object[] displayMethodAttributes =
displayMethod.GetCustomAttributes(typeof(HelpAttribute), false);
if (displayMethodAttributes.Length > 0)
Más información
Puede explorar más sobre C# con uno de nuestros tutoriales.
Anterior
Introducción a C#
Artículo • 10/02/2023 • Tiempo de lectura: 3 minutos
Puede probar estos tutoriales en entornos diferentes. Los conceptos que aprenderá son
los mismos. La diferencia estará en el tipo de experiencia que elija:
Hola mundo
En el tutorial Hola mundo, creará el programa de C# más básico. Explorará el tipo
string y cómo trabajar con texto. También puede usar la ruta de acceso en
Microsoft Learn o en Jupyter desde Binder .
Números en C#
En el tutorial Números en C#, obtendrá información sobre cómo se almacenan los
números en los equipos y cómo realizar cálculos con distintos tipos numéricos.
Conocerá los datos básicos sobre cómo realizar redondeos y cálculos matemáticos con
C#. Este tutorial también está disponible para ejecutarse localmente en su máquina.
Bifurcaciones y bucles
En el tutorial Ramas y bucles se explican los datos básicos sobre la selección de
diferentes rutas de acceso de la ejecución del código en función de los valores
almacenados en variables. Aprenderá los datos básicos del flujo de control, es decir,
cómo los programas toman decisiones y eligen distintas acciones. Este tutorial también
está disponible para ejecutarse localmente en su máquina.
En este tutorial se asume que ha completado las lecciones Hola mundo y Números en
C#.
Colección de listas
En la lección Colección de listas se ofrece información general sobre el tipo de colección
de listas que almacena secuencias de datos. Se explica cómo agregar y quitar
elementos, buscarlos y ordenar las listas. Explorará los diferentes tipos de listas. Este
tutorial también está disponible para ejecutarse localmente en su máquina.
101 ejemplos de LINQ
Este ejemplo requiere la herramienta global dotnet-try . Una vez que instale la
herramienta y clone el repositorio try-samples , puede aprender Language Integrated
Query (LINQ) mediante un conjunto de 101 ejemplos que puede ejecutar de forma
interactiva. Puede descubrir diferentes maneras de consultar, explorar y transformar
secuencias de datos.
Configuración de un entorno local
Artículo • 22/09/2022 • Tiempo de lectura: 2 minutos
dotnet new crea una aplicación. Este comando genera los archivos y los recursos
necesarios para la aplicación. Los tutoriales de introducción a C# usan el tipo de
aplicación console . Cuando conozca los conceptos básicos, puede expandirlo a
otros tipos de aplicaciones.
dotnet build compila el archivo ejecutable.
dotnet run ejecuta el archivo ejecutable.
Números en C#
En el tutorial Números en C#, obtendrá información sobre cómo se almacenan los
números en los equipos y cómo realizar cálculos con distintos tipos numéricos.
Conocerá los datos básicos sobre cómo realizar redondeos y cálculos matemáticos con
C#.
Bifurcaciones y bucles
En el tutorial Ramas y bucles se explican los datos básicos sobre la selección de
diferentes rutas de acceso de la ejecución del código en función de los valores
almacenados en variables. Aprenderá los datos básicos del flujo de control, es decir,
cómo los programas toman decisiones y eligen distintas acciones.
En este tutorial se supone que ha completado las lecciones Hola mundo y Números en
C#.
Colección de listas
En la lección Colección de listas se ofrece información general sobre el tipo de colección
de listas que almacena secuencias de datos. Se explica cómo agregar y quitar
elementos, buscarlos y ordenar las listas. Explorará los diferentes tipos de listas.
En este tutorial se explican los tipos numéricos en C#. Escribirá pequeñas cantidades de
código y luego compilará y ejecutará ese código. El tutorial contiene una serie de
lecciones que ofrecen información detallada sobre los números y las operaciones
matemáticas en C#. En ellas se enseñan los aspectos básicos del lenguaje C#.
Requisitos previos
En el tutorial se espera que tenga una máquina configurada para el desarrollo local.
Consulte Configuración del entorno local para obtener instrucciones de instalación e
información general sobre el desarrollo de aplicaciones en .NET.
CLI de .NET
) Importante
Las plantillas de C# para .NET 6 usan instrucciones de nivel superior. Es posible que
la aplicación no coincida con el código de este artículo si ya ha actualizado a
.NET 6. Para obtener más información, consulte el artículo Las nuevas plantillas de
C# generan instrucciones de nivel superior.
Microsoft.NET.Sdk
Microsoft.NET.Sdk.Web
Microsoft.NET.Sdk.Worker
Estas directivas de global using implícitas incluyen los espacios de nombres más
comunes para el tipo de proyecto.
C#
int a = 18;
int b = 6;
int c = a + b;
Console.WriteLine(c);
Ha visto una de las operaciones matemáticas fundamentales con enteros. El tipo int
representa un entero, que puede ser cero o un número entero positivo o negativo. Use
el símbolo + para la suma. Otros operadores matemáticos comunes con enteros son:
- para resta
* para multiplicación
/ para división
Comience por explorar esas operaciones diferentes. Agregue estas líneas después de la
línea que escribe el valor de c :
C#
// subtraction
c = a - b;
Console.WriteLine(c);
// multiplication
c = a * b;
Console.WriteLine(c);
// division
c = a / b;
Console.WriteLine(c);
Sugerencia
C#
WorkWithIntegers();
void WorkWithIntegers()
{
int a = 18;
int b = 6;
int c = a + b;
Console.WriteLine(c);
// subtraction
c = a - b;
Console.WriteLine(c);
// multiplication
c = a * b;
Console.WriteLine(c);
// division
c = a / b;
Console.WriteLine(c);
C#
//WorkWithIntegers();
El // inicia un comentario en C#. Los comentarios son cualquier texto que desea
mantener en el código fuente pero que no se ejecuta como código. El compilador no
genera ningún código ejecutable a partir de comentarios. Dado que WorkWithIntegers()
es un método, solo tiene que comentar una línea.
C#
int a = 5;
int b = 4;
int c = 2;
int d = a + b * c;
Console.WriteLine(d);
C#
d = (a + b) * c;
Console.WriteLine(d);
Combine muchas operaciones distintas para indagar más. Agregue algo similar a las
líneas siguientes. Pruebe dotnet run de nuevo.
C#
d = (a + b) - 6 * c + (12 * 4) / 3 + 12;
Console.WriteLine(d);
C#
int e = 7;
int f = 4;
int g = 3;
int h = (e + f) / g;
Console.WriteLine(h);
Antes de continuar, vamos a tomar todo el código que ha escrito en esta sección y a
colocarlo en un nuevo método. Llame a ese nuevo método OrderPrecedence . El código
debería tener este aspecto:
C#
// WorkWithIntegers();
OrderPrecedence();
void WorkWithIntegers()
{
int a = 18;
int b = 6;
int c = a + b;
Console.WriteLine(c);
// subtraction
c = a - b;
Console.WriteLine(c);
// multiplication
c = a * b;
Console.WriteLine(c);
// division
c = a / b;
Console.WriteLine(c);
void OrderPrecedence()
int a = 5;
int b = 4;
int c = 2;
int d = a + b * c;
Console.WriteLine(d);
d = (a + b) * c;
Console.WriteLine(d);
d = (a + b) - 6 * c + (12 * 4) / 3 + 12;
Console.WriteLine(d);
int e = 7;
int f = 4;
int g = 3;
int h = (e + f) / g;
Console.WriteLine(h);
C#
int a = 7;
int b = 4;
int c = 3;
int d = (a + b) / c;
int e = (a + b) % c;
Console.WriteLine($"quotient: {d}");
Console.WriteLine($"remainder: {e}");
C#
Si un cálculo genera un valor que supera los límites, se producirá una condición de
subdesbordamiento o desbordamiento. La respuesta parece ajustarse de un límite al
otro. Agregue estas dos líneas para ver un ejemplo:
C#
Tenga en cuenta que la respuesta está muy próxima al entero mínimo (negativo). Es lo
mismo que min + 2 . La operación de suma desbordó los valores permitidos para los
enteros. La respuesta es un número negativo muy grande porque un desbordamiento
"se ajusta" desde el valor de entero más alto posible al más bajo.
Hay otros tipos numéricos con distintos límites y precisiones que podría usar si el tipo
int no satisface sus necesidades. Vamos a explorar ahora esos otros tipos. Antes de
comenzar la siguiente sección, mueva el código que escribió en esta sección a un
método independiente. Denomínelo TestLimits .
C#
double a = 5;
double b = 4;
double c = 2;
double d = (a + b) / c;
Console.WriteLine(d);
Tenga en cuenta que la respuesta incluye la parte decimal del cociente. Pruebe una
expresión algo más complicada con tipos double:
C#
double e = 19;
double f = 23;
double g = 8;
double h = (e + f) / g;
Console.WriteLine(h);
El intervalo de un valor double es mucho más amplio que en el caso de los valores
enteros. Pruebe el código siguiente debajo del que ha escrito hasta ahora:
C#
C#
Console.WriteLine(third);
Desafío
C#
decimal min = decimal.MinValue;
Tenga en cuenta que el intervalo es más pequeño que con el tipo double . Puede
observar una precisión mayor con el tipo decimal si prueba el siguiente código:
C#
double a = 1.0;
double b = 3.0;
Console.WriteLine(a / b);
decimal c = 1.0M;
decimal d = 3.0M;
Console.WriteLine(c / d);
El sufijo M en los números es la forma de indicar que una constante debe usar el tipo
decimal . De no ser así, el compilador asume el tipo de double .
7 Nota
La letra M se eligió como la letra más distintiva visualmente entre las palabras clave
double y decimal .
Observe que la expresión matemática con el tipo decimal tiene más dígitos a la derecha
del punto decimal.
Desafío
Ahora que ya conoce los diferentes tipos numéricos, escriba código para calcular el área
de un círculo cuyo radio sea de 2,50 centímetros. Recuerde que el área de un circulo es
igual al valor de su radio elevado al cuadrado multiplicado por Pi. Sugerencia: .NET
contiene una constante de Pi, Math.PI, que puede usar para ese valor. Math.PI, al igual
que todas las constantes declaradas en el espacio de nombres System.Math , es un valor
double . Por ese motivo, debe usar double en lugar de valores decimal para este desafío.
Debe obtener una respuesta entre 19 y 20. Puede comprobar la respuesta si consulta el
ejemplo de código terminado en GitHub .
En este tutorial se enseña a escribir código de C# que analiza variables y cambia la ruta
de acceso de ejecución en función de dichas variables. Escriba código de C# y vea los
resultados de la compilación y la ejecución. El tutorial contiene una serie de lecciones en
las que se analizan las construcciones de bifurcaciones y bucles en C#. En ellas se
enseñan los aspectos básicos del lenguaje C#.
Sugerencia
Para pegar un fragmento de código dentro del modo de enfoque , debe usar el
método abreviado de teclado ( Ctrl + v o cmd + v ).
Requisitos previos
En el tutorial se espera que tenga una máquina configurada para el desarrollo local.
Consulte Configuración del entorno local para obtener instrucciones de instalación e
información general sobre el desarrollo de aplicaciones en .NET.
Si prefiere ejecutar el código sin tener que configurar un entorno local, consulte la
versión interactiva en el explorador de este tutorial.
CLI de .NET
) Importante
Las plantillas de C# para .NET 6 usan instrucciones de nivel superior. Es posible que
la aplicación no coincida con el código de este artículo si ya ha actualizado a
.NET 6. Para obtener más información, consulte el artículo Las nuevas plantillas de
C# generan instrucciones de nivel superior.
Microsoft.NET.Sdk
Microsoft.NET.Sdk.Web
Microsoft.NET.Sdk.Worker
Estas directivas de global using implícitas incluyen los espacios de nombres más
comunes para el tipo de proyecto.
Este comando crea una nueva aplicación de consola de .NET en el directorio actual. Abra
Program.cs en su editor favorito y reemplace el contenido por el código siguiente:
C#
int a = 5;
int b = 6;
if (a + b > 10)
Pruebe este código escribiendo dotnet run en la ventana de la consola. Debería ver el
mensaje "La respuesta es mayor que 10", impreso en la consola. Modifique la
declaración de b para que el resultado de la suma sea menor que diez:
C#
int b = 3;
Escriba dotnet run de nuevo. Como la respuesta es menor que diez, no se imprime
nada. La condición que está probando es false. No tiene ningún código para ejecutar
porque solo ha escrito una de las bifurcaciones posibles para una instrucción if : la
bifurcación true.
Sugerencia
C#
int a = 5;
int b = 3;
if (a + b > 10)
else
) Importante
Dado que la sangría no es significativa, debe usar { y } para indicar si desea que más
de una instrucción forme parte del bloque que se ejecuta de forma condicional. Los
programadores de C# suelen usar esas llaves en todas las cláusulas if y else . El
siguiente ejemplo es igual que el que acaba de crear. Modifique el código anterior para
que coincida con el código siguiente:
C#
int a = 5;
int b = 3;
if (a + b > 10)
else
Sugerencia
En el resto de este tutorial, todos los ejemplos de código incluyen las llaves, según
las prácticas aceptadas.
Puede probar condiciones más complicadas. Agregue el código siguiente después del
que ha escrito hasta ahora:
C#
int c = 4;
else
&& representa "y". Significa que ambas condiciones deben cumplirse para ejecutar la
C#
if ((a + b + c > 10) || (a == b))
else
Modifique los valores de a , b y c y cambie entre && y || para explorar. Obtendrá más
conocimientos sobre el funcionamiento de los operadores && y || .
C#
ExploreIf();
void ExploreIf()
int a = 5;
int b = 3;
if (a + b > 10)
else
int c = 4;
else
else
C#
//ExploreIf();
El // inicia un comentario en C#. Los comentarios son cualquier texto que desea
mantener en el código fuente pero que no se ejecuta como código. El compilador no
genera ningún código ejecutable a partir de comentarios.
C#
int counter = 0;
counter++;
En este ejemplo aparece otro operador nuevo. El código ++ que aparece después de la
variable counter es el operador de incremento. Suma un valor de uno al valor de
counter y almacena dicho valor en la variable de counter .
) Importante
Asegúrese de que la condición del bucle while cambia a false mientras ejecuta el
código. En caso contrario, se crea un bucle infinito donde nunca finaliza el
programa. Esto no está demostrado en este ejemplo, ya que tendrá que forzar al
programa a cerrar mediante CTRL-C u otros medios.
El bucle while prueba la condición antes de ejecutar el código que sigue a while . El
bucle do ... while primero ejecuta el código y después comprueba la condición. El bucle
do while se muestra en el código siguiente:
C#
int counter = 0;
do
counter++;
C#
El código anterior funciona de la misma forma que los bucles while y do que ya ha
usado. La instrucción for consta de tres partes que controlan su funcionamiento.
La parte central es la condición de for: index < 10 declara que este bucle for debe
continuar ejecutándose mientras que el valor del contador sea menor que diez.
La última parte es el iterador de for: index++ especifica cómo modificar la variable de
bucle después de ejecutar el bloque que sigue a la instrucción for . En este caso,
especifica que index debe incrementarse en uno cada vez que el bloque se ejecuta.
Cuando haya terminado, escriba algo de código para practicar con lo que ha aprendido.
Hay otra instrucción de bucle que no se trata en este tutorial: la instrucción foreach . La
instrucción foreach repite su instrucción con cada elemento de una secuencia de
elementos. Se usa más a menudo con colecciones, por lo que se trata en el siguiente
tutorial.
C#
C#
C#
for (int row = 1; row < 11; row++)
Puede ver que el bucle externo se incrementa una vez con cada ejecución completa del
bucle interno. Invierta el anidamiento de filas y columnas, y vea los cambios por sí
mismo. Cuando haya terminado, coloque el código de esta sección en un método
denominado ExploreLoops() .
Pruébelo usted mismo. Después, revise cómo lo ha hecho. Debe obtener 63 como
respuesta. Puede ver una respuesta posible mediante la visualización del código
completado en GitHub .
Instrucciones de selección
Instrucciones de iteración
Aprenda a administrar colecciones de
datos mediante List<T> en C#
Artículo • 07/03/2023 • Tiempo de lectura: 6 minutos
Requisitos previos
En el tutorial se espera que tenga una máquina configurada para el desarrollo local.
Consulte Configuración del entorno local para obtener instrucciones de instalación e
información general sobre el desarrollo de aplicaciones en .NET.
Si prefiere ejecutar el código sin tener que configurar un entorno local, consulte la
versión interactiva en el explorador de este tutorial.
) Importante
Las plantillas de C# para .NET 6 usan instrucciones de nivel superior. Es posible que
la aplicación no coincida con el código de este artículo si ya ha actualizado a
.NET 6. Para obtener más información, consulte el artículo Las nuevas plantillas de
C# generan instrucciones de nivel superior.
Microsoft.NET.Sdk
Microsoft.NET.Sdk.Web
Microsoft.NET.Sdk.Worker
Estas directivas de global using implícitas incluyen los espacios de nombres más
comunes para el tipo de proyecto.
Console.WriteLine($"Hello {name.ToUpper()}!");
Reemplace <name> por su propio nombre. Guarde Program.cs. Escriba dotnet run en la
ventana de la consola para probarlo.
Ha creado una lista de cadenas, ha agregado tres nombres a esa lista y ha impreso los
nombres en MAYÚSCULAS. Los conceptos aplicados ya se han aprendido en los
tutoriales anteriores para recorrer en bucle la lista.
Un aspecto importante de este tipo List<T> es que se puede aumentar o reducir, lo que
permite agregar o quitar elementos. Agregue este código al final del programa:
C#
Console.WriteLine();
names.Add("Maria");
names.Add("Bill");
names.Remove("Ana");
Console.WriteLine($"Hello {name.ToUpper()}!");
Se han agregado dos nombres más al final de la lista. También se ha quitado uno.
Guarde el archivo y escriba dotnet run para probarlo.
List<T> también permite hacer referencia a elementos individuales a través del índice.
Coloque el índice entre los tokens [ y ] después del nombre de la lista. C# utiliza 0
para el primer índice. Agregue este código directamente después del código que acaba
de agregar y pruébelo:
C#
No se puede acceder a un índice si se coloca después del final de la lista. Recuerde que
los índices empiezan en 0, por lo que el índice más grande válido es uno menos que el
número de elementos de la lista. Puede comprobar durante cuánto tiempo la lista usa la
propiedad Count. Agregue el código siguiente al final del programa:
C#
Guarde el archivo y vuelva a escribir dotnet run para ver los resultados.
C#
if (index == -1)
else
if (index == -1)
else
Los elementos de la lista también se pueden ordenar. El método Sort clasifica todos los
elementos de la lista en su orden normal (por orden alfabético si se trata de cadenas).
Agregue este código a la parte inferior del programa:
C#
names.Sort();
Console.WriteLine($"Hello {name.ToUpper()}!");
Guarde el archivo y escriba dotnet run para probar esta última versión.
C#
WorkWithStrings();
void WorkWithStrings()
Console.WriteLine($"Hello {name.ToUpper()}!");
Console.WriteLine();
names.Add("Maria");
names.Add("Bill");
names.Remove("Ana");
Console.WriteLine($"Hello {name.ToUpper()}!");
if (index == -1)
else
if (index == -1)
else
names.Sort();
Console.WriteLine($"Hello {name.ToUpper()}!");
C#
Se crea una lista de enteros y se definen los dos primeros enteros con el valor 1. Son los
dos primeros valores de una sucesión de Fibonacci, una secuencia de números. Cada
número sucesivo de Fibonacci se obtiene con la suma de los dos números anteriores.
Agregue este código:
C#
fibonacciNumbers.Add(previous + previous2);
Console.WriteLine(item);
Sugerencia
Para centrarse solo en esta sección, puede comentar el código que llama a
WorkWithStrings(); . Solo debe colocar dos caracteres / delante de la llamada,
Desafío
Trate de recopilar los conceptos que ha aprendido en esta lección y en las anteriores.
Amplíe lo que ha creado hasta el momento con los números de Fibonacci. Pruebe a
escribir el código para generar los veinte primeros números de la secuencia. (Como
sugerencia, el 20º número de la serie de Fibonacci es 6765).
Desafío completo
Puede ver un ejemplo de solución en el ejemplo de código terminado en GitHub .
Con cada iteración del bucle, se obtienen los dos últimos enteros de la lista, se suman y
se agrega el valor resultante a la lista. El bucle se repite hasta que se hayan agregado
veinte elementos a la lista.
Enhorabuena, ha completado el tutorial sobre las listas. Puede seguir estos tutoriales
adicionales en su propio entorno de desarrollo.
Puede obtener más información sobre cómo trabajar con el tipo List en el artículo de
los aspectos básicos de .NET que trata sobre las colecciones. Ahí también podrá conocer
muchos otros tipos de colecciones.
Estructura general de un programa de
C#
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Los programas de C# constan de uno o más archivos. Cada archivo contiene cero o más
espacios de nombres. Un espacio de nombres contiene tipos como clases, estructuras,
interfaces, enumeraciones y delegados, u otros espacios de nombres. El siguiente
ejemplo es el esqueleto de un programa de C# que contiene todos estos elementos.
C#
// A skeleton of a C# program
using System;
Console.WriteLine("Hello world!");
namespace YourNamespace
class YourClass
struct YourStruct
interface IYourInterface
enum YourEnum
namespace YourNestedNamespace
struct YourStruct
C#
// A skeleton of a C# program
using System;
namespace YourNamespace
class YourClass
struct YourStruct
interface IYourInterface
enum YourEnum
namespace YourNestedNamespace
struct YourStruct
class Program
Console.WriteLine("Hello world!");
Secciones relacionadas
Obtenga información sobre estos elementos del programa en la sección de tipos de la
guía de aspectos básicos:
Clases
Structs
Espacios de nombres
Interfaces
Enumeraciones
Delegados
Solo puede haber un punto de entrada en un programa de C#. Si hay más de una clase
que tenga un método Main , deberá compilar el programa con la opción del compilador
StartupObject para especificar qué método Main quiere utilizar como punto de entrada.
Para obtener más información, consulte StartupObject (opciones del compilador de C#).
C#
class TestClass
Console.WriteLine(args.Length);
C#
using System.Text;
builder.AppendLine("Hello");
builder.AppendLine("World!");
Console.WriteLine(builder.ToString());
Para obtener información sobre cómo escribir código de aplicación con un método de
punto de entrada implícito, consulte las instrucciones de nivel superior.
Información general
El método Main es el punto de entrada de un programa ejecutable; es donde se
inicia y finaliza el control del programa.
Main se declara dentro de una clase o estructura. El valor de Main debe ser static y
C#
Al agregar los tipos de valor devuelto async , Task y Task<int> , se simplifica el código
de programa cuando las aplicaciones de consola tienen que realizar tareas de inicio y
await de operaciones asincrónicas en Main .
Cree una aplicación mediante la ejecución de dotnet new console . Modifique el método
Main en Program.cs como se indica a continuación:
C#
class MainReturnValTest
//...
return 0;
Dado que el código devuelve el valor cero, el archivo por lotes comunicará un resultado
satisfactorio. En cambio, si cambia MainReturnValTest.cs para que devuelva un valor
distinto de cero y luego vuelve a compilar el programa, la ejecución posterior del script
de PowerShell informará de que se ha producido un error.
PowerShell
dotnet run
if ($LastExitCode -eq 0) {
} else
Resultados
Execution succeeded
Return value = 0
C#
public static void Main()
AsyncConsoleWork().GetAwaiter().GetResult();
return 0;
C#
Una ventaja de declarar Main como async es que el compilador siempre genera el
código correcto.
7 Nota
No hay valores devueltos, se usa await static async Task Main(string[] args)
Si no se usan los argumentos, puede omitir args de la signatura del método para
simplificar ligeramente el código:
7 Nota
El parámetro del método Main es una matriz String que representa los argumentos de la
línea de comandos. Normalmente, para determinar si hay argumentos, se prueba la
propiedad Length ; por ejemplo:
C#
if (args.Length == 0)
return 1;
Sugerencia
La matriz args no puede ser NULL. así que es seguro acceder a la propiedad
Length sin comprobar los valores NULL.
C#
También se puede usar el tipo de C# long , que tiene como alias Int64 :
C#
También puede usar el método ToInt64 de la clase Convert para hacer lo mismo:
C#
Para compilar y ejecutar la aplicación desde un símbolo del sistema, siga estos pasos:
1. Pegue el código siguiente en cualquier editor de texto, y después guarde el
archivo como archivo de texto con el nombre Factorial.cs.
C#
return -1;
long tempResult = 1;
tempResult *= i;
return tempResult;
class MainClass
if (args.Length == 0)
return 1;
// num = int.Parse(args[0]);
int num;
if (!test)
return 1;
// Calculate factorial.
// Print result.
if (result == -1)
else
return 0;
2. En la pantalla Inicio o en el menú Inicio, abra una ventana del Símbolo del sistema
para desarrolladores de Visual Studio y navegue hasta la carpeta que contiene el
archivo que creó.
dotnet build
dotnet run -- 3
7 Nota
Vea también
System.Environment
Procedimiento para mostrar argumentos de la línea de comandos
Instrucciones de nivel superior:
programas sin métodos Main
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
C#
Console.WriteLine("Hello World!");
Las instrucciones de nivel superior permiten escribir programas sencillos para utilidades
pequeñas, como Azure Functions y Acciones de GitHub. También facilitan a los nuevos
programadores de C# empezar a aprender y escribir código.
En las secciones siguientes se explican las reglas de lo que puede y no puede hacer con
las instrucciones de nivel superior.
Directivas using
Si incluye directivas using, deben aparecer en primer lugar en el archivo, como en este
ejemplo:
C#
using System.Text;
builder.AppendLine("Hello");
builder.AppendLine("World!");
Console.WriteLine(builder.ToString());
C#
MyClass.TestMethod();
MyNamespace.MyClass.MyMethod();
Console.WriteLine("Hello World!");
namespace MyNamespace
class MyClass
args
Las instrucciones de nivel superior pueden hacer referencia a la variable args para
acceder a los argumentos de línea de comandos que se hayan escrito. La variable args
nunca es NULL, pero su valor Length es cero si no se han proporcionado argumentos de
línea de comandos. Por ejemplo:
C#
if (args.Length > 0)
Console.WriteLine($"Argument={arg}");
else
Console.WriteLine("No arguments");
await
Puede llamar a un método asincrónico mediante el uso await . Por ejemplo:
C#
Console.Write("Hello ");
await Task.Delay(5000);
Console.WriteLine("World!");
C#
string? s = Console.ReadLine();
return returnValue;
El compilador usa información de tipo para garantizar que todas las operaciones que se
realizan en el código cuentan con seguridad de tipos. Por ejemplo, si declara una variable
de tipo int, el compilador le permite usar la variable en operaciones de suma y resta. Si
intenta realizar esas mismas operaciones en una variable de tipo bool, el compilador
genera un error, como se muestra en el siguiente ejemplo:
C#
int a = 5;
int b = a + 2; //OK
int c = a + test;
7 Nota
Los desarrolladores de C y C++ deben tener en cuenta que, en C#, bool no se
puede convertir en int .
C#
// Declaration only:
float temperature;
string name;
MyClass myClass;
int[] source = { 0, 1, 2, 3, 4, 5 };
select item;
C#
return names[ID];
else
return String.Empty;
Después de declarar una variable, no se puede volver a declarar con un nuevo tipo y no
se puede asignar un valor que no sea compatible con su tipo declarado. Por ejemplo, no
puede declarar un valor int y, luego, asignarle un valor booleano de true . En cambio,
los valores se pueden convertir en otros tipos, por ejemplo, cuando se asignan a
variables nuevas o se pasan como argumentos de método. El compilador realiza
automáticamente una conversión de tipo que no da lugar a una pérdida de datos. Una
conversión que pueda dar lugar a la pérdida de datos requiere un valor cast en el
código fuente.
Tipos integrados
C# proporciona un conjunto estándar de tipos integrados. Estos representan números
enteros, valores de punto flotante, expresiones booleanas, caracteres de texto, valores
decimales y otros tipos de datos. También hay tipos string y object integrados. Estos
tipos están disponibles para su uso en cualquier programa de C#. Para obtener una lista
completa de los tipos integrados, vea Tipos integrados.
Tipos personalizados
Puede usar las construcciones struct, class, interface, enum y record para crear sus
propios tipos personalizados. La biblioteca de clases .NET es en sí misma una colección
de tipos personalizados que puede usar en sus propias aplicaciones. De forma
predeterminada, los tipos usados con más frecuencia en la biblioteca de clases están
disponibles en cualquier programa de C#. Otros están disponibles solo cuando agrega
explícitamente una referencia de proyecto al ensamblado que los define. Una vez que el
compilador tenga una referencia al ensamblado, puede declarar variables (y constantes)
de los tipos declarados en dicho ensamblado en el código fuente. Para más información,
vea Biblioteca de clases .NET.
En la ilustración siguiente se muestra la relación entre los tipos de valor y los tipos de
referencia en CTS.
7 Nota
Puede ver que los tipos utilizados con mayor frecuencia están organizados en el
espacio de nombres System. Sin embargo, el espacio de nombres que contiene un
tipo no tiene ninguna relación con un tipo de valor o un tipo de referencia.
Las clases (class) y estructuras (struct) son dos de las construcciones básicas de Common
Type System en .NET. C# 9 agrega registros, que son un tipo de clase. Cada una de ellas
es básicamente una estructura de datos que encapsula un conjunto de datos y
comportamientos que forman un conjunto como una unidad lógica. Los datos y
comportamientos son los miembros de la clase, estructura o registro. Los miembros
incluyen sus métodos, propiedades y eventos, entre otros elementos, como se muestra
más adelante en este artículo.
Una declaración de clase, estructura o registro es como un plano que se utiliza para
crear instancias u objetos en tiempo de ejecución. Si define una clase, una estructura o
un registro denominado Person , Person es el nombre del tipo. Si declara e inicializa una
variable p de tipo Person , se dice que p es un objeto o instancia de Person . Se pueden
crear varias instancias del mismo tipo Person , y cada instancia tiene diferentes valores
en sus propiedades y campos.
Una clase es un tipo de referencia. Cuando se crea un objeto del tipo, la variable a la
que se asigna el objeto contiene solo una referencia a esa memoria. Cuando la
referencia de objeto se asigna a una nueva variable, la nueva variable hace referencia al
objeto original. Los cambios realizados en una variable se reflejan en la otra variable
porque ambas hacen referencia a los mismos datos.
Una estructura es un tipo de valor. Cuando se crea una estructura, la variable a la que se
asigna la estructura contiene los datos reales de ella. Cuando la estructura se asigna a
una nueva variable, se copia. Por lo tanto, la nueva variable y la variable original
contienen dos copias independientes de los mismos datos. Los cambios realizados en
una copia no afectan a la otra copia.
Los tipos de registro pueden ser tipos de referencia ( record class ) o tipos de valor
( record struct ).
En general, las clases se utilizan para modelar comportamientos más complejos. Las
clases suelen almacenar datos que están diseñados para modificarse después de crear
un objeto de clase. Los structs son más adecuados para estructuras de datos pequeñas.
Los structs suelen almacenar datos que no están diseñados para modificarse después de
que se haya creado el struct. Los tipos de registro son estructuras de datos con
miembros sintetizados del compilador adicionales. Los registros suelen almacenar datos
que no están diseñados para modificarse después de que se haya creado el objeto.
Tipos de valor
Los tipos de valor derivan de System.ValueType, el cual deriva de System.Object. Los
tipos que derivan de System.ValueType tienen un comportamiento especial en CLR. Las
variables de tipo de valor contienen directamente sus valores. La memoria de un struct
se asigna en línea en cualquier contexto en el que se declare la variable. No se produce
ninguna asignación del montón independiente ni sobrecarga de la recolección de
elementos no utilizados para las variables de tipo de valor. Puede declarar tipos record
struct que son tipos de valor e incluir los miembros sintetizados para los registros.
Los tipos numéricos integrados son structs y tienen campos y métodos a los que se
puede acceder:
C#
byte b = byte.MaxValue;
Pero se declaran y se les asignan valores como si fueran tipos simples no agregados:
C#
int i = 5;
char c = 'Z';
Los tipos de valor están sellados. No se puede derivar un tipo de cualquier tipo de valor,
por ejemplo System.Int32. No se puede definir un struct para que herede de cualquier
clase o struct definido por el usuario porque un struct solo puede heredar de
System.ValueType. A pesar de ello, un struct puede implementar una o más interfaces.
No se puede convertir un tipo de struct en cualquier tipo de interfaz que implemente.
Esta conversión provoca una que operación boxing encapsule el struct dentro de un
objeto de tipo de referencia en el montón administrado. Las operaciones de conversión
boxing se producen cuando se pasa un tipo de valor a un método que toma
System.Object o cualquier tipo de interfaz como parámetro de entrada. Para obtener
más información, vea Conversión boxing y unboxing.
Puede usar la palabra clave struct para crear sus propios tipos de valor personalizados.
Normalmente, un struct se usa como un contenedor para un pequeño conjunto de
variables relacionadas, como se muestra en el ejemplo siguiente:
C#
public int x, y;
x = p1;
y = p2;
Para más información sobre estructuras, vea Tipos de estructura. Para más información
sobre los tipos de valor, vea Tipos de valor.
C#
CreateNew = 1,
Create = 2,
Open = 3,
OpenOrCreate = 4,
Truncate = 5,
Append = 6,
Tipos de referencia
Un tipo que se define como class , record , delegate, matriz o interface es un reference
type.
Al declarar una variable de un reference type, contiene el valor null hasta que se asigna
con una instancia de ese tipo o se crea una mediante el operador new. La creación y
asignación de una clase se muestran en el ejemplo siguiente:
C#
C#
Todas las matrices son tipos de referencia, incluso si sus elementos son tipos de valor.
Las matrices derivan de manera implícita de la clase System.Array. El usuario las declara
y las usa con la sintaxis simplificada que proporciona C#, como se muestra en el
ejemplo siguiente:
C#
int[] nums = { 1, 2, 3, 4, 5 };
Los tipos de referencia admiten la herencia completamente. Al crear una clase, puede
heredar de cualquier otra interfaz o clase que no esté definida como sellado. Otras
clases pueden heredar de la clase e invalidar sus métodos virtuales. Para obtener más
información sobre cómo crear sus clases, vea Clases, estructuras y registros. Para más
información sobre la herencia y los métodos virtuales, vea Herencia.
Dado que los literales tienen tipo y todos los tipos derivan en última instancia de
System.Object, puede escribir y compilar código como el siguiente:
C#
Console.WriteLine(s);
// Outputs: "System.Int32"
Console.WriteLine(type);
Tipos genéricos
Los tipos se pueden declarar con uno o varios parámetros de tipo que actúan como un
marcador de posición para el tipo real (el tipo concreto). El código de cliente
proporciona el tipo concreto cuando crea una instancia del tipo. Estos tipos se
denominan tipos genéricos. Por ejemplo, el tipo de .NET
System.Collections.Generic.List<T> tiene un parámetro de tipo al que, por convención,
se le denomina T . Cuando crea una instancia del tipo, especifica el tipo de los objetos
que contendrá la lista, por ejemplo, string :
C#
List<string> stringList = new List<string>();
stringList.Add("String example");
// compile time error adding a type other than a string:
stringList.Add(4);
El uso del parámetro de tipo permite reutilizar la misma clase para incluir cualquier tipo
de elemento, sin necesidad de convertir cada elemento en object. Las clases de
colección genéricas se denominan colecciones con establecimiento inflexible de tipos
porque el compilador conoce el tipo específico de los elementos de la colección y
puede generar un error en tiempo de compilación si, por ejemplo, intenta agregar un
valor entero al objeto stringList del ejemplo anterior. Para más información, vea
Genéricos.
En algunos casos, resulta conveniente crear un tipo con nombre para conjuntos sencillos
de valores relacionados que no desea almacenar ni pasar fuera de los límites del
método. Puede crear tipos anónimos para este fin. Para obtener más información,
consulte Tipos anónimos (Guía de programación de C#).
Los tipos de valor normales no pueden tener un valor null, pero se pueden crear tipos de
valor que aceptan valores NULL mediante la adición de ? después del tipo. Por ejemplo,
int? es un tipo int que también puede tener el valor null. Los tipos que admiten un
valor NULL son instancias del tipo struct genérico System.Nullable<T>. Los tipos que
admiten un valor NULL son especialmente útiles cuando hay un intercambio de datos
con bases de datos en las que los valores numéricos podrían ser null . Para más
información, vea Tipos que admiten un valor NULL.
C#
C#
Si los dos tipos son diferentes para una variable, es importante comprender cuándo se
aplican el tipo en tiempo de compilación y el tipo en tiempo de ejecución. El tipo en
tiempo de compilación determina todas las acciones realizadas por el compilador. Estas
acciones del compilador incluyen la resolución de llamadas a métodos, la resolución de
sobrecarga y las conversiones implícitas y explícitas disponibles. El tipo en tiempo de
ejecución determina todas las acciones que se resuelven en tiempo de ejecución. Estas
acciones de tiempo de ejecución incluyen el envío de llamadas a métodos virtuales, la
evaluación de expresiones is y switch y otras API de prueba de tipos. Para comprender
mejor cómo interactúa el código con los tipos, debe reconocer qué acción se aplica a
cada tipo.
Secciones relacionadas
Para más información, consulte los siguientes artículos.
Tipos integrados
Tipos de valor
Tipos de referencia
Especificación del lenguaje C#
Para obtener más información, consulte la Especificación del lenguaje C#. La
especificación del lenguaje es la fuente definitiva de la sintaxis y el uso de C#.
Declaración de espacios de nombres
para organizar los tipos
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
C#
System.Console.WriteLine("Hello World!");
C#
using System;
C#
Console.WriteLine("Hello World!");
) Importante
Las plantillas de C# para .NET 6 usan instrucciones de nivel superior. Es posible que
la aplicación no coincida con el código de este artículo si ya ha actualizado a
.NET 6. Para obtener más información, consulte el artículo Las nuevas plantillas de
C# generan instrucciones de nivel superior.
Microsoft.NET.Sdk
Microsoft.NET.Sdk.Web
Microsoft.NET.Sdk.Worker
Estas directivas de global using implícitas incluyen los espacios de nombres más
comunes para el tipo de proyecto.
En segundo lugar, declarar sus propios espacios de nombres puede ayudarle a controlar
el ámbito de nombres de clase y método en proyectos de programación grandes. Use la
palabra clave namespace para declarar un espacio de nombres, como en el ejemplo
siguiente:
C#
namespace SampleNamespace
class SampleClass
System.Console.WriteLine(
A partir de C# 10, puede declarar un espacio de nombres para todos los tipos definidos
en ese archivo, como se muestra en el ejemplo siguiente:
C#
namespace SampleNamespace;
class AnotherSampleClass
System.Console.WriteLine(
"SampleMethod inside SampleNamespace");
La ventaja de esta nueva sintaxis es que es más sencilla, lo que ahorra espacio horizontal
y llaves. Esto facilita la lectura del código.
Tipos de referencia
Un tipo que se define como una class, es un tipo de referencia. Al declarar una variable
de un tipo de referencia en tiempo de ejecución, esta contendrá el valor null hasta que
se cree expresamente una instancia de la clase mediante el operador new o se le asigne
un objeto de un tipo compatible que se ha creado en otro lugar, tal y como se muestra
en el ejemplo siguiente:
C#
//Declaring another object of the same type, assigning it the value of the
first object.
Declarar clases
Las clases se declaran mediante la palabra clave class seguida por un identificador
único, como se muestra en el siguiente ejemplo:
C#
La palabra clave class va precedida del nivel de acceso. Como en este caso se usa
public, cualquier usuario puede crear instancias de esta clase. El nombre de la clase
sigue a la palabra clave class . El nombre de la clase debe ser un nombre de
identificador de C# válido. El resto de la definición es el cuerpo de la clase, donde se
definen los datos y el comportamiento. Los campos, las propiedades, los métodos y los
eventos de una clase se denominan de manera colectiva miembros de clase.
Creación de objetos
Aunque a veces se usan indistintamente, una clase y un objeto son cosas diferentes. Una
clase define un tipo de objeto, pero no es un objeto en sí. Un objeto es una entidad
concreta basada en una clase y, a veces, se conoce como una instancia de una clase.
Los objetos se pueden crear usando la palabra clave new , seguida del nombre de la
clase en la que se basará el objeto, como en este ejemplo:
C#
Cuando se crea una instancia de una clase, se vuelve a pasar al programador una
referencia al objeto. En el ejemplo anterior, object1 es una referencia a un objeto que
se basa en Customer . Esta referencia apunta al objeto nuevo, pero no contiene los datos
del objeto. De hecho, puede crear una referencia de objeto sin tener que crear ningún
objeto:
C#
Customer object2;
C#
Este código crea dos referencias de objeto que hacen referencia al mismo objeto. Por lo
tanto, los cambios efectuados en el objeto mediante object3 se reflejan en los usos
posteriores de object4 . Dado que los objetos basados en clases se tratan por referencia,
las clases se denominan "tipos de referencia".
Herencia de clases
Las clases admiten completamente la herencia, una característica fundamental de la
programación orientada a objetos. Al crear una clase, puede heredar de cualquier otra
que no esté definida como sealed, y otras clases pueden heredar de esa e invalidar sus
métodos virtuales. Además, puede implementar una o varias interfaces.
C#
Cuando una clase declara una clase base, hereda todos los miembros de la clase base
excepto los constructores. Para obtener más información, vea Herencia.
Una clase de C# solo puede heredar directamente de una clase base. En cambio, dado
que una clase base puede heredar de otra clase, una clase podría heredar
indirectamente varias clases base. Además, una clase puede implementar directamente
una o varias interfaces. Para obtener más información, vea Interfaces.
Una clase puede declararse abstract. Una clase abstracta contiene métodos abstractos
que tienen una definición de firma, pero no tienen ninguna implementación. No se
pueden crear instancias de las clases abstractas. Solo se pueden usar a través de las
clases derivadas que implementan los métodos abstractos. Por el contrario, la clase
sealed no permite que otras clases se deriven de ella. Para más información, vea Clases y
miembros de clase abstractos y sellados.
Las definiciones de clase se pueden dividir entre distintos archivos de código fuente.
Para más información, vea Clases y métodos parciales.
Ejemplo
En el ejemplo siguiente se define una clase pública que contiene una propiedad
implementada automáticamente, un método y un método especial denominado
constructor. Para obtener más información, consulte los artículos Propiedades, Métodos
y Constructores. Luego, se crea una instancia de las instancias de la clase con la palabra
clave new .
C#
using System;
public Person()
Name = "unknown";
Name = name;
return Name;
class TestPerson
Console.WriteLine(person1.Name);
Console.WriteLine(person2.Name);
Console.WriteLine(person2);
// Output:
// unknown
// Sarah Jones
// Sarah Jones
Igualdad de valores
En el caso de los registros, la igualdad de valores significa que dos variables de un tipo
de registro son iguales si los tipos coinciden y todos los valores de propiedad y campo
coinciden. Para otros tipos de referencia, como las clases, la igualdad significa igualdad
de referencias. Es decir, dos variables de un tipo de clase son iguales si hacen referencia
al mismo objeto. Los métodos y operadores que determinan la igualdad de dos
instancias de registro usan la igualdad de valores.
No todos los modelos de datos funcionan bien con la igualdad de valores. Por ejemplo,
Entity Framework Core depende de la igualdad de referencias para garantizar que solo
usa una instancia de un tipo de entidad para lo que es conceptualmente una entidad.
Por esta razón, los tipos de registro no son adecuados para su uso como tipos de
entidad en Entity Framework Core.
Inmutabilidad
Un tipo inmutable es aquél que impide cambiar cualquier valor de propiedad o campo
de un objeto una vez creada su instancia. La inmutabilidad puede ser útil cuando se
necesita que un tipo sea seguro para los subprocesos o si depende de que un código
hash quede igual en una tabla hash. Los registros proporcionan una sintaxis concisa
para crear y trabajar con tipos inmutables.
La inmutabilidad no es adecuada para todos los escenarios de datos. Por ejemplo, Entity
Framework Core no admite la actualización con tipos de entidad inmutables.
sintaxis para expresar las relaciones de herencia. Los registros se diferencian de las
clases de las siguientes maneras:
Puede usar parámetros posicionales para crear un tipo y sus instancias con
propiedades inmutables.
Los mismos métodos y operadores que indican la igualdad o desigualdad de la
referencia en las clases (como Object.Equals(Object) y == ), indican la igualdad o
desigualdad de valores en los registros.
Puede usar una expresión with para crear una copia de un objeto inmutable con
nuevos valores en las propiedades seleccionadas.
El método ToString de un registro crea una cadena con formato que muestra el
nombre de tipo de un objeto y los nombres y valores de todas sus propiedades
públicas.
Un registro puede heredar de otro registro. Un registro no puede heredar de una
clase y una clase no puede heredar de un registro.
Ejemplos
En el ejemplo siguiente se define un registro público que usa parámetros posicionales
para declarar y crear instancias de un registro. A continuación, imprime el nombre de
tipo y los valores de propiedad:
C#
Console.WriteLine(person);
C#
person1.PhoneNumbers[0] = "555-1234";
En el ejemplo siguiente se muestra el uso de una expresión with para copiar un objeto
inmutable y cambiar una de las propiedades:
C#
Console.WriteLine(person1);
Console.WriteLine(person2);
Console.WriteLine(person2);
Para definir una interfaz, deberá usar la palabra clave interface, tal y como se muestra en
el ejemplo siguiente.
C#
interface IEquatable<T>
Para obtener más información sobre las clases abstractas, vea Clases y miembros de
clase abstractos y sellados (Guía de programación de C#).
Las interfaces pueden contener propiedades, eventos, indizadores o métodos de
instancia, o bien cualquier combinación de estos cuatro tipos de miembros. Las
interfaces pueden contener constructores estáticos, campos, constantes u operadores. A
partir de C# 11, los miembros de interfaz que no son campos pueden ser static
abstract . Una interfaz no puede contener campos de instancia, constructores de
7 Nota
Cuando una interfaz declara miembros estáticos, un tipo que implementa esa
interfaz también puede declarar miembros estáticos con la misma firma. Los
miembros son distintos y se identifican de forma única mediante el tipo que
declara el miembro. El miembro estático declarado en un tipo no reemplaza el
miembro estático que se declara en la interfaz.
Una clase o estructura que implementa una interfaz debe proporcionar una
implementación para todos los miembros declarados sin una implementación
predeterminada proporcionada por la interfaz. Sin embargo, si una clase base
implementa una interfaz, cualquier clase que se derive de la clase base hereda esta
implementación.
C#
Las propiedades y los indizadores de una clase pueden definir descriptores de acceso
adicionales para una propiedad o indizador que estén definidos en una interfaz. Por
ejemplo, una interfaz puede declarar una propiedad que tenga un descriptor de acceso
get. La clase que implementa la interfaz puede declarar la misma propiedad con un
descriptor de acceso get y set. Sin embargo, si la propiedad o el indizador usan una
implementación explícita, los descriptores de acceso deben coincidir. Para obtener más
información sobre la implementación explícita, vea Implementación de interfaz explícita
y Propiedades de interfaces.
Las interfaces pueden heredar de una o varias interfaces. La interfaz derivada hereda los
miembros de sus interfaces base. Una clase que implementa una interfaz derivada debe
implementar todos los miembros de esta, incluidos los de las interfaces base. Esa clase
puede convertirse implícitamente en la interfaz derivada o en cualquiera de sus
interfaces base. Una clase puede incluir una interfaz varias veces mediante las clases
base que hereda o mediante las interfaces que otras interfaces heredan. Sin embargo, la
clase puede proporcionar una implementación de una interfaz solo una vez y solo si la
clase declara la interfaz como parte de la definición de la clase ( class ClassName :
InterfaceName ). Si la interfaz se hereda porque se heredó una clase base que
Una clase base también puede implementar miembros de interfaz mediante el uso de
los miembros virtuales. En ese caso, una clase derivada puede cambiar el
comportamiento de la interfaz reemplazando los miembros virtuales. Para obtener más
información sobre los miembros virtuales, vea Polimorfismo.
Resumen de interfaces
Una interfaz tiene las propiedades siguientes:
En las versiones de C# anteriores a la 8.0, una interfaz es como una clase base
abstracta con solo miembros abstractos. Cualquier clase o estructura que
implemente la interfaz debe implementar todos sus miembros.
A partir de la versión 8.0 de C#, una interfaz puede definir implementaciones
predeterminadas para algunos o todos sus miembros. Una clase o estructura que
implemente la interfaz no tiene que implementar los miembros que tengan
implementaciones predeterminadas. Para obtener más información, vea Métodos
de interfaz predeterminados.
No se puede crear una instancia de una interfaz directamente. Sus miembros se
implementan por medio de cualquier clase o estructura que implementa la
interfaz.
Una clase o estructura puede implementar varias interfaces. Una clase puede
heredar una clase base y también implementar una o varias interfaces.
Clases y métodos genéricos
Artículo • 10/01/2023 • Tiempo de lectura: 3 minutos
C#
class TestGenericList
list1.Add(1);
list2.Add("");
list3.Add(new ExampleClass());
T está disponible para la clase Node anidada. Cuando se crea una instancia de
C#
public Node(T t)
next = null;
data = t;
private T data;
public T Data
// constructor
public GenericList()
head = null;
n.Next = head;
head = n;
current = current.Next;
C#
class TestGenericList
list.AddHead(x);
System.Console.WriteLine("\nDone");
Vea también
System.Collections.Generic
Elementos genéricos en .NET
Tipos anónimos
Artículo • 03/03/2023 • Tiempo de lectura: 5 minutos
Los tipos anónimos son una manera cómoda de encapsular un conjunto de propiedades
de solo lectura en un único objeto sin tener que definir primero un tipo explícitamente.
El compilador genera el nombre del tipo y no está disponible en el nivel de código
fuente. El compilador deduce el tipo de cada propiedad.
Para crear tipos anónimos, use el operador new con un inicializador de objeto. Para
obtener más información sobre los inicializadores de objeto, vea Inicializadores de
objeto y colección (Guía de programación de C#).
C#
// Rest the mouse pointer over v.Amount and v.Message in the following
// statement to verify that their inferred types are int and string.
Console.WriteLine(v.Amount + v.Message);
Los tipos anónimos suelen usarse en la cláusula select de una expresión de consulta
para devolver un subconjunto de las propiedades de cada objeto en la secuencia de
origen. Para más información sobre las consultas, vea LINQ en C#.
Los tipos anónimos contienen una o varias propiedades públicas de solo lectura. No es
válido ningún otro tipo de miembros de clase, como métodos o eventos. La expresión
que se usa para inicializar una propiedad no puede ser null , una función anónima o un
tipo de puntero.
El escenario más habitual es inicializar un tipo anónimo con propiedades de otro tipo.
En el siguiente ejemplo, se da por hecho que existe una clase con el nombre Product . La
clase Product incluye las propiedades Color y Price , junto con otras propiedades que
no son de su interés. La variable products es una colección de objetos Product . La
declaración de tipo anónimo comienza con la palabra clave new . La declaración inicializa
un nuevo tipo que solo usa dos propiedades de Product . El uso de tipos anónimos hace
que la consulta devuelva una cantidad de datos menor.
C#
var productQuery =
Sugerencia
Puede usar la regla de estilo de .NET IDE0037 para aplicar si se prefieren los
nombres de miembros inferidos o explícitos.
También es posible definir un campo por objeto de otro tipo: clase, estructura o incluso
otro tipo anónimo. Se realiza mediante el uso de la variable que contiene este objeto
igual que en el ejemplo siguiente, donde se crean dos tipos anónimos mediante tipos
definidos por el usuario para los que ya se han creado instancias. En ambos casos, el
campo product del tipo anónimo shipment y shipmentWithBonus será de tipo Product
que contiene los valores predeterminados de cada campo. Y el campo bonus será de
tipo anónimo creado por el compilador.
C#
Normalmente, cuando se usa un tipo anónimo para inicializar una variable, la variable se
declara como variable local con tipo implícito mediante var. El nombre del tipo no se
puede especificar en la declaración de la variable porque solo el compilador tiene
acceso al nombre subyacente del tipo anónimo. Para obtener más información sobre
var , vea Variables locales con asignación implícita de tipos.
Puede crear una matriz de elementos con tipo anónimo combinando una variable local
con tipo implícito y una matriz con tipo implícito, como se muestra en el ejemplo
siguiente.
C#
Los tipos anónimos son tipos class que derivan directamente de object y que no se
pueden convertir a ningún tipo excepto object. El compilador proporciona un nombre
para cada tipo anónimo, aunque la aplicación no pueda acceder a él. Desde el punto de
vista de Common Language Runtime, un tipo anónimo no es diferente de otros tipos de
referencia.
C#
Console.WriteLine(apple);
Console.WriteLine(onSale);
Encapsulación
A veces se hace referencia a la encapsulación como el primer pilar o principio de la
programación orientada a objetos. Una clase o una estructura pueden especificar hasta
qué punto se puede acceder a sus miembros para codificar fuera de la clase o la
estructura. No se prevé el uso de los métodos y las variables fuera de la clase, o el
ensamblado puede ocultarse para limitar el potencial de los errores de codificación o de
los ataques malintencionados. Para más información, consulte el tutorial Programación
orientada a objetos.
Miembros
Los miembros de un tipo incluyen todos los métodos, campos, constantes, propiedades
y eventos. En C#, no hay métodos ni variables globales como en otros lenguajes. Incluso
se debe declarar el punto de entrada de un programa, el método Main , dentro de una
clase o estructura (de forma implícita cuando usa instrucciones de nivel superior).
La lista siguiente incluye los diversos tipos de miembros que se pueden declarar en una
clase, estructura o registro.
Campos
Constantes
Propiedades
Métodos
Constructores
Events
Finalizadores
Indexadores
Operadores
Tipos anidados
Accesibilidad
Algunos métodos y propiedades están diseñados para ser invocables y accesibles desde
el código fuera de una clase o estructura, lo que se conoce como código de cliente.
Otros métodos y propiedades pueden estar indicados exclusivamente para utilizarse en
la propia clase o estructura. Es importante limitar la accesibilidad del código, a fin de
que solo el código de cliente previsto pueda acceder a él. Puede usar los siguientes
modificadores de acceso para especificar hasta qué punto los tipos y sus miembros son
accesibles para el código de cliente:
public
protected
internal
protected internal
private
private protected
Herencia
Las clases (pero no las estructuras) admiten el concepto de herencia. Una clase que
deriva de otra clase (denominada clase base) contiene automáticamente todos los
miembros públicos, protegidos e internos de la clase base, salvo sus constructores y
finalizadores.
Las clases pueden declararse como abstract, lo que significa que uno o varios de sus
métodos no tienen ninguna implementación. Aunque no se pueden crear instancias de
clases abstractas directamente, pueden servir como clases base para otras clases que
proporcionan la implementación que falta. Las clases también pueden declararse como
sealed para evitar que otras clases hereden de ellas.
Interfaces
Las clases, las estructuras y los registros pueden implementar varias interfaces.
Implementar de una interfaz significa que el tipo implementa todos los métodos
definidos en la interfaz. Para más información, vea Interfaces.
Tipos genéricos
Las clases, las estructuras y los registros pueden definirse con uno o varios parámetros
de tipo. El código de cliente proporciona el tipo cuando crea una instancia del tipo. Por
ejemplo, la clase List<T> del espacio de nombres System.Collections.Generic se define
con un parámetro de tipo. El código de cliente crea una instancia de List<string> o
List<int> para especificar el tipo que contendrá la lista. Para más información, vea
Genéricos.
Tipos estáticos
Las clases (pero no las estructuras ni los registros) pueden declararse como static . Una
clase estática puede contener solo miembros estáticos y no se puede crear una instancia
de ellos con la palabra clave new . Una copia de la clase se carga en memoria cuando se
carga el programa, y sus miembros son accesibles a través del nombre de clase. Las
clases, las estructuras y los registros pueden contener miembros estáticos. Para obtener
más información, vea Clases estáticas y sus miembros.
Tipos anidados
Una clase, estructura o registro se puede anidar dentro de otra clase, estructura o
registro. Para obtener más información, consulte Tipos anidados.
Tipos parciales
Puede definir parte de una clase, estructura o método en un archivo de código y otra
parte en un archivo de código independiente. Para más información, vea Clases y
métodos parciales.
Inicializadores de objeto
Puede crear instancias e inicializar objetos de clase o estructura, así como colecciones
de objetos, asignando valores a sus propiedades. Para más información, consulte
Procedimiento para inicializar un objeto mediante un inicializador de objeto.
Tipos anónimos
En situaciones donde no es conveniente o necesario crear una clase con nombre, utilice
los tipos anónimos. Los tipos anónimos se definen mediante sus miembros de datos con
nombre. Para obtener más información, consulte Tipos anónimos (Guía de
programación de C#).
Métodos de extensión
Puede "extender" una clase sin crear una clase derivada mediante la creación de un tipo
independiente. Ese tipo contiene métodos a los que se puede llamar como si
perteneciesen al tipo original. Para más información, consulte Métodos de extensión.
Registros
C# 9 presenta el tipo record , un tipo de referencia que se puede crear en lugar de una
clase o una estructura. Los registros son clases con un comportamiento integrado para
encapsular datos en tipos inmutables. C# 10 presenta el tipo de valor record struct . Un
registro ( record class o record struct ) proporciona las siguientes características:
Una definición de clase o estructura es como un plano que especifica qué puede hacer
el tipo. Un objeto es básicamente un bloque de memoria que se ha asignado y
configurado de acuerdo con el plano. Un programa puede crear muchos objetos de la
misma clase. Los objetos también se denominan instancias y pueden almacenarse en
una variable con nombre, o en una matriz o colección. El código de cliente es el código
que usa estas variables para llamar a los métodos y acceder a las propiedades públicas
del objeto. En un lenguaje orientado a objetos, como C#, un programa típico consta de
varios objetos que interactúan dinámicamente.
7 Nota
Los tipos estáticos se comportan de forma diferente a lo que se describe aquí. Para
más información, vea Clases estáticas y sus miembros.
C#
Name = name;
Age = age;
class Program
person2.Name = "Molly";
person2.Age = 16;
/*
Output:
Dado que las estructuras son tipos de valor, una variable de un objeto de estructura
contiene una copia de todo el objeto. También se pueden crear instancias de estructuras
usando el operador new , pero esto no resulta necesario, como se muestra en el ejemplo
siguiente:
C#
namespace Example;
Name = name;
Age = age;
Person p2 = p1;
p2.Name = "Spencer";
p2.Age = 7;
/*
Output:
*/
7 Nota
C#
//{
// {
// Name = name;
// Age = age;
// }
//}
p2.Name = "Wallace";
p2.Age = 75;
if (p2.Equals(p1))
Secciones relacionadas
Para obtener más información:
Clases
Constructores
Finalizadores
Eventos
object
Herencia
class
Tipos de estructura
new (operador)
Sistema de tipos comunes
Herencia: deriva tipos para crear un
comportamiento más especializado
Artículo • 15/02/2023 • Tiempo de lectura: 7 minutos
ClassA .
7 Nota
Cuando se define una clase para que derive de otra clase, la clase derivada obtiene
implícitamente todos los miembros de la clase base, salvo sus constructores y sus
finalizadores. La clase derivada reutiliza el código de la clase base sin tener que volver a
implementarlo. Puede agregar más miembros en la clase derivada. La clase derivada
amplía la funcionalidad de la clase base.
C#
// Static field currentID stores the job ID of the last WorkItem that
//Properties.
// implicitly.
public WorkItem()
ID = 0;
this.ID = GetNextID();
this.Title = title;
this.Description = desc;
this.jobLength = joblen;
// Method Update enables you to update the title and job length of an
this.Title = title;
this.jobLength = joblen;
// from System.Object.
$"{this.ID} - {this.Title}";
public ChangeRequest() { }
int originalID)
// from WorkItem.
this.ID = GetNextID();
this.Title = title;
this.Description = desc;
this.jobLength = jobLen;
// of WorkItem.
this.originalItemID = originalID;
C#
1);
Console.WriteLine(item.ToString());
// ChangeRequest object.
Console.WriteLine(change.ToString());
/* Output:
1 - Fix Bugs
*/
Interfaces
Una interfaz es un tipo de referencia que define un conjunto de miembros. Todas las
clases y estructuras que implementan esa interfaz deben implementar ese conjunto de
miembros. Una interfaz puede definir una implementación predeterminada para todos o
ninguno de estos miembros. Una clase puede implementar varias interfaces, aunque
solo puede derivar de una única clase base directa.
Las interfaces se usan para definir funciones específicas para clases que no tienen
necesariamente una relación "es un/una". Por ejemplo, la interfaz System.IEquatable<T>
se puede implementar mediante cualquier clase o estructura para determinar si dos
objetos del tipo son equivalentes (pero el tipo define la equivalencia). IEquatable<T> no
implica el mismo tipo de relación "es un/una" que existe entre una clase base y una
clase derivada (por ejemplo, Mammal es Animal ). Para más información, vea Interfaces.
En tiempo de ejecución, los objetos de una clase derivada pueden ser tratados
como objetos de una clase base en lugares como parámetros de métodos y
colecciones o matrices. Cuando se produce este polimorfismo, el tipo declarado
del objeto ya no es idéntico a su tipo en tiempo de ejecución.
Las clases base pueden definir e implementar métodosvirtuales, y las clases
derivadas pueden invalidarlos, lo que significa que pueden proporcionar su propia
definición e implementación. En tiempo de ejecución, cuando el código de cliente
llama al método, CLR busca el tipo en tiempo de ejecución del objeto e invoca esa
invalidación del método virtual. En el código fuente puede llamar a un método en
una clase base y hacer que se ejecute una versión del método de la clase derivada.
Los métodos virtuales permiten trabajar con grupos de objetos relacionados de manera
uniforme. Por ejemplo, supongamos que tiene una aplicación de dibujo que permite a
un usuario crear varios tipos de formas en una superficie de dibujo. En tiempo de
compilación, no sabe qué tipos de formas en concreto creará el usuario. Sin embargo, la
aplicación tiene que realizar el seguimiento de los distintos tipos de formas que se
crean, y tiene que actualizarlos en respuesta a las acciones del mouse del usuario. Para
solucionar este problema en dos pasos básicos, puede usar el polimorfismo:
1. Crear una jerarquía de clases en la que cada clase de forma específica deriva de
una clase base común.
2. Usar un método virtual para invocar el método apropiado en una clase derivada
mediante una sola llamada al método de la clase base.
Primero, cree una clase base llamada Shape y clases derivadas como Rectangle , Circle
y Triangle . Dé a la clase Shape un método virtual llamado Draw e invalídelo en cada
clase derivada para dibujar la forma determinada que la clase representa. Cree un
objeto List<Shape> y agréguele una instancia de Circle , Triangle y Rectangle .
C#
// Virtual method
Console.WriteLine("Drawing a circle");
base.Draw();
Console.WriteLine("Drawing a rectangle");
base.Draw();
Console.WriteLine("Drawing a triangle");
base.Draw();
Para actualizar la superficie de dibujo, use un bucle foreach para iterar por la lista y
llamar al método Draw en cada objeto Shape de la lista. Aunque cada objeto de la lista
tenga un tipo declarado de Shape , se invocará el tipo en tiempo de ejecución (la versión
invalidada del método en cada clase derivada).
C#
new Rectangle(),
new Triangle(),
new Circle()
};
shape.Draw();
/* Output:
Drawing a rectangle
Drawing a triangle
Drawing a circle
*/
En C#, cada tipo es polimórfico porque todos los tipos, incluidos los definidos por el
usuario, heredan de Object.
Introducción al polimorfismo
Miembros virtuales
Cuando una clase derivada hereda de una clase base, incluye todos los miembros de la
clase base. Todo el comportamiento declarado en la clase base forma parte de la clase
derivada. Esto permite que los objetos de la clase derivada se traten como objetos de la
clase base. Los modificadores de acceso ( public , protected , private etc.) determinan si
esos miembros son accesibles desde la implementación de la clase derivada. Los
métodos virtuales proporcionan al diseñador diferentes opciones para el
comportamiento de la clase derivada:
La clase derivada puede invalidar los miembros virtuales de la clase base, y definir
un comportamiento nuevo.
La clase derivada hereda el método de clase base más cercano sin invalidarlo para
conservar el comportamiento existente, pero permite que más clases derivadas
invaliden el método.
La clase derivada puede definir una nueva implementación no virtual de esos
miembros que oculte las implementaciones de la clase base.
Una clase derivada puede invalidar un miembro de la clase base si este se declara como
virtual o abstracto. El miembro derivado debe usar la palabra clave override para indicar
explícitamente que el propósito del método es participar en una invocación virtual. El
siguiente fragmento de código muestra un ejemplo:
C#
get { return 0; }
get { return 0; }
Los campos no pueden ser virtuales; solo pueden serlo los métodos, propiedades,
eventos e indizadores. Cuando una clase derivada invalida un miembro virtual, se llama
a ese miembro aunque se acceda a una instancia de esa clase como una instancia de la
clase base. El siguiente fragmento de código muestra un ejemplo:
C#
BaseClass A = B;
Los métodos y propiedades virtuales permiten a las clases derivadas extender una clase
base sin necesidad de usar la implementación de clase base de un método. Para obtener
más información, consulte Control de versiones con las palabras clave Override y New.
Una interfaz proporciona otra manera de definir un método o conjunto de métodos
cuya implementación se deja a las clases derivadas.
C#
get { return 0; }
get { return 0; }
Se puede acceder a los miembros de la clase base ocultos desde el código de cliente si
se convierte la instancia de la clase derivada en una instancia de la clase base. Por
ejemplo:
C#
BaseClass A = (BaseClass)B;
public class A
public class B : A
Una clase derivada puede detener la herencia virtual al declarar una invalidación como
sealed. Para detener la herencia, es necesario colocar la palabra clave sealed antes de la
palabra clave override en la declaración del miembro de la clase. El siguiente
fragmento de código muestra un ejemplo:
C#
public class C : B
C#
public class D : C
En este caso, si se llama a DoWork en D con una variable de tipo D , se llama a la nueva
instancia de DoWork . Si se usa una variable de tipo C , B o A para acceder a una
instancia de D , la llamada a DoWork seguirá las reglas de herencia virtual y enrutará esas
llamadas a la implementación de DoWork en la clase C .
C#
//...
base.DoWork();
7 Nota
En este artículo encontrará información general sobre los escenarios en los que puede
usar la coincidencia de patrones. Estas técnicas pueden mejorar la legibilidad y la
corrección del código. Para ver una explicación detallada de todos los patrones que
puede aplicar, consulte el artículo sobre patrones en la referencia del lenguaje.
C#
else
C#
Console.WriteLine(message);
Pruebas de tipo
Otro uso común de la coincidencia de patrones consiste en probar una variable para ver
si coincide con un tipo determinado. Por ejemplo, el código siguiente comprueba si una
variable no es null e implementa la interfaz System.Collections.Generic.IList<T>. Si es así,
usa la propiedad ICollection<T>.Count de esa lista para buscar el índice central. El
patrón de declaración no coincide con un valor null , independientemente del tipo de
tiempo de compilación de la variable. El código siguiente protege contra null , además
de proteger contra un tipo que no implementa IList .
C#
else
return sequence.Skip(halfLength).First();
Se pueden aplicar las mismas pruebas en una expresión switch para probar una
variable con varios tipos diferentes. Puede usar esa información para crear algoritmos
mejores basados en el tipo de tiempo de ejecución específico.
C#
command switch
};
C#
command switch
};
C#
command switch
};
En todos estos ejemplos, el patrón de descarte le garantiza que controlará todas las
entradas. El compilador le ayuda a asegurarse de que se controlan todos los valores de
entrada posibles.
Patrones relacionales
Puede usar patrones relacionales para probar cómo se compara un valor con las
constantes. Por ejemplo, el código siguiente devuelve el estado del agua en función de
la temperatura en Fahrenheit:
C#
tempInFahrenheit switch
};
El código anterior también muestra el and patrón lógico conjuntivo para comprobar que
ambos patrones relacionales coincidan. También puede usar un patrón or disyuntivo
para comprobar que cualquiera de los patrones coincide. Los dos patrones relacionales
están entre paréntesis, que se pueden usar alrededor de cualquier patrón para mayor
claridad. Los dos últimos segmentos modificadores controlan los casos del punto de
fusión y el punto de ebullición. Sin esos dos segmentos, el compilador le advertirá de
que la lógica no abarca todas las entradas posibles.
C#
tempInFahrenheit switch
_ => "gas",
};
Varias entradas
Todos los patrones que ha visto hasta ahora comprueban una entrada. Puede escribir
patrones que examinen varias propiedades de un objeto. Fíjese en el siguiente registro
Order :
C#
C#
order switch
};
Los dos primeros segmentos examinan dos propiedades de Order . El tercero examina
solo el costo. El siguiente realiza la comprobación de null y el último coincide con
cualquier otro valor. Si el tipo Order define un método Deconstruct adecuado, puede
omitir los nombres de propiedad del patrón y usar la desconstrucción para examinar las
propiedades:
C#
order switch
};
Patrones de lista
Puede comprobar los elementos de una lista o una matriz mediante un patrón de lista.
Un patrón de lista proporciona una forma de aplicar un patrón a cualquier elemento de
una secuencia. Además, puede aplicar el patrón de descarte ( _ ) para que coincida con
cualquier elemento, o bien un patrón de sector para que coincida con ninguno o más
elementos.
Los patrones de lista son una herramienta valiosa cuando los datos no siguen una
estructura normal. Puede usar la coincidencia de patrones para probar la forma y los
valores de los datos en vez de transformarlos en un conjunto de objetos.
Resultados
Es un formato CSV, pero algunas de las filas tienen más columnas que otras. También
hay algo incluso peor para el procesamiento, una columna del tipo WITHDRAWAL que
tiene texto generado por el usuario y puede contener una coma en el texto. Un patrón
de lista que incluye el patrón de descarte, el patrón constante y el patrón var para
capturar los datos de los procesos del valor en este formato:
C#
};
En este artículo se proporciona una introducción a los tipos de código que se pueden
escribir con la coincidencia de patrones en C#. En los artículos siguientes se muestran
más ejemplos del uso de patrones en escenarios y un vocabulario completo de patrones
disponibles.
Vea también
Uso de la coincidencia de patrones para evitar la comprobación de "is" seguida de
una conversión (reglas de estilo IDE0020 e IDE0038)
Exploración: Uso de la coincidencia de patrones para crear el comportamiento de
la clase y mejorar el código
Tutorial: Uso de la coincidencia de patrones para compilar algoritmos basados en
tipos y basados en datos
Referencia: Coincidencia de patrones
Descartes: aspectos básicos de C#
Artículo • 03/03/2023 • Tiempo de lectura: 9 minutos
Los descartes aclaran la intención del código. Un descarte indica que el código nunca
usa la variable. Mejoran la legibilidad y el mantenimiento.
Para indicar que una variable es un descarte, se le asigna como nombre el carácter de
subrayado ( _ ). Por ejemplo, la siguiente llamada de método devuelve una tupla en la
que el primer y el segundo valor son descartes. area es una variable declarada
previamente establecida en el tercer componente devuelto por GetCityInformation :
C#
A partir de C# 9.0, se pueden usar descartes para especificar los parámetros de entrada
de una expresión lambda que no se utilizan. Para más información, consulte sección
sobre parámetros de entrada de una expresión lambda en el artículo sobre expresiones
lambda.
C#
double area = 0;
area = 468.48;
if (year1 == 1960)
population1 = 7781984;
if (year2 == 2010)
population2 = 8175133;
Para obtener más información sobre la deconstrucción de tuplas con descartes, vea
Deconstructing tuples and other types (Deconstruir tuplas y otros tipos).
C#
using System;
namespace Discards
FirstName = fname;
MiddleName = mname;
LastName = lname;
City = cityName;
State = stateName;
fname = FirstName;
lname = LastName;
fname = FirstName;
mname = MiddleName;
lname = LastName;
fname = FirstName;
lname = LastName;
city = City;
state = State;
class Example
Para obtener más información sobre la deconstrucción de tipos definidos por el usuario
con descartes, vea Deconstructing tuples and other types (Deconstruir tuplas y otros
tipos).
C#
CultureInfo.CurrentCulture.DateTimeFormat,
CultureInfo.CurrentCulture.NumberFormat,
ProvidesFormatInfo(obj);
Console.WriteLine(obj switch
null => "A null object reference: Its use could result in a
NullReferenceException",
});
// System.Globalization.CultureInfo object
// System.Globalization.DateTimeFormatInfo object
// System.Globalization.NumberFormatInfo object
C#
"2018-05-01T14:57:32.8375298-04:00", "5/01/2018",
Console.WriteLine($"'{dateString}': valid");
else
Console.WriteLine($"'{dateString}': invalid");
// '2018-05-01T14:57:32.8375298-04:00': valid
// '5/01/2018': valid
Descarte independiente
Puede usar un descarte independiente para indicar cualquier variable que decida omitir.
Un uso típico es usar una asignación para asegurarse de que un argumento no sea
NULL. En el código siguiente se usa un descarte para forzar una asignación. El lado
derecho de la asignación utiliza el operador de uso combinado de NULL para producir
System.ArgumentNullException cuando el argumento es null . El código no necesita el
resultado de la asignación, por lo que se descarta. La expresión fuerza una
comprobación nula. El descarte aclara su intención: el resultado de la asignación no es
necesario ni se usa.
C#
C#
_ = Task.Run(() =>
var iterations = 0;
iterations++;
});
await Task.Delay(5000);
Sin asignar la tarea a un descarte, el código siguiente genera una advertencia del
compilador:
C#
Task.Run(() =>
var iterations = 0;
iterations++;
});
await Task.Delay(5000);
7 Nota
C#
byte[] arr = { 0, 0, 1, 2 };
_ = BitConverter.ToInt32(arr, 0);
Console.WriteLine(_);
// 33619968
C#
int newValue = 0;
return _ == newValue;
Error del compilador CS0136: "Una variable local o un parámetro denominados '_'
no se pueden declarar en este ámbito porque ese nombre se está usando en un
ámbito local envolvente para definir una variable local o un parámetro". Por
ejemplo:
C#
// error CS0136:
Consulte también
Eliminación de valores de expresión innecesarios (regla de estilo IDE0058)
Eliminación de la asignación de valores innecesarios (regla de estilo IDE0059)
Eliminación del parámetro sin usar (regla de estilo IDE0060)
Deconstruir tuplas y otros tipos
is operator
Expresión switch
Deconstruir tuplas y otros tipos
Artículo • 15/02/2023 • Tiempo de lectura: 11 minutos
Una tupla proporciona una manera ligera de recuperar varios valores de una llamada de
método. Pero una vez que recupere la tupla, deberá controlar sus elementos
individuales. Trabajar elemento a elemento es complicado, como se muestra en el
ejemplo siguiente. El método QueryCityData devuelve una tupla de tres y cada uno de
sus elementos se asigna a una variable en una operación aparte.
C#
Puede recuperar varios elementos de una tupla o recuperar varios campos, propiedades
y valores calculados de un objeto en una sola operación de deconstrucción. Para
deconstruir una tupla, asigne sus elementos a variables individuales. Cuando se
deconstruye un objeto, los valores seleccionados se asignan a variables individuales.
Tuplas
C# incluye compatibilidad integrada para deconstruir tuplas, lo que permite
desempaquetar todos los elementos de una tupla en una sola operación. La sintaxis
general para deconstruir una tupla es parecida a la sintaxis para definirla, ya que las
variables a las que se va a asignar cada elemento se escriben entre paréntesis en el lado
izquierdo de una instrucción de asignación. Por ejemplo, la siguiente instrucción asigna
los elementos de una tupla de cuatro a cuatro variables distintas:
C#
C#
Puede usar la palabra clave var para que C# deduzca el tipo de cada variable.
Debe colocar la palabra clave var fuera de los paréntesis. En el ejemplo siguiente
se usa la inferencia de tipos al deconstruir la tupla de tres devuelta por el método
QueryCityData .
C#
También se puede usar la palabra clave var individualmente con alguna de las
declaraciones de variable, o todas, dentro de los paréntesis.
C#
public static void Main()
C#
C#
No se puede especificar un tipo específico fuera de los paréntesis aunque cada campo
de la tupla tenga el mismo tipo. Al hacerlo, se genera el error del compilador CS8136:
"La forma de deconstrucción "var (...)" no permite un tipo específico para "var'".
Debe asignar cada elemento de la tupla a una variable. Si omite algún elemento, el
compilador genera el error CS8132: "No se puede deconstruir una tupla de "x"
elementos en "y" variables".
Elementos de tupla con descartes
A menudo, cuando se deconstruye una tupla, solo interesan los valores de algunos
elementos. Puede aprovechar la compatibilidad de C# con los descartes, que son
variables de solo escritura cuyos valores se decide omitir. Un carácter de subrayado ("_")
elige un descarte en una asignación. Puede descartar tantos valores como quiera; todos
se representan mediante el descarte único _ .
C#
using System;
double area = 0;
area = 468.48;
if (year1 == 1960)
population1 = 7781984;
if (year2 == 2010)
population2 = 8175133;
apellidos:
C#
public void Deconstruct(out string fname, out string mname, out string
lname)
C#
C#
using System;
FirstName = fname;
MiddleName = mname;
LastName = lname;
City = cityName;
State = stateName;
fname = FirstName;
lname = LastName;
public void Deconstruct(out string fname, out string mname, out string
lname)
fname = FirstName;
mname = MiddleName;
lname = LastName;
fname = FirstName;
lname = LastName;
city = City;
state = State;
C#
C#
using System;
using System.Collections.Generic;
using System.Reflection;
isReadOnly = ! p.CanWrite;
isStatic = getter.IsStatic;
if (p.CanRead)
getter = p.GetMethod;
if (p.CanWrite)
setter = p.SetMethod;
hasGetAndSet = true;
if (getter != null)
if (getter.IsPublic)
getAccessTemp = "public";
else if (getter.IsPrivate)
getAccessTemp = "private";
else if (getter.IsAssembly)
getAccessTemp = "internal";
else if (getter.IsFamily)
getAccessTemp = "protected";
else if (getter.IsFamilyOrAssembly)
if (setter != null)
if (setter.IsPublic)
setAccessTemp = "public";
else if (setter.IsPrivate)
setAccessTemp = "private";
else if (setter.IsAssembly)
setAccessTemp = "internal";
else if (setter.IsFamily)
setAccessTemp = "protected";
else if (setter.IsFamilyOrAssembly)
if (setAccessTemp == getAccessTemp)
sameAccess = true;
access = getAccessTemp;
else
access = null;
getAccess = getAccessTemp;
setAccess = setAccessTemp;
Console.WriteLine($"\nThe {dateType.FullName}.{prop.Name}
property:");
prop = listType.GetProperty("Item",
BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
if (!hasGetAndSet | sameAccess)
Console.WriteLine(accessibility);
else
// PropertyType: DateTime
// Static: True
// Read-only: True
// Indexed: False
//
C#
["https://github.com/dotnet/docs"] = 16_465,
["https://github.com/dotnet/runtime"] = 114_223,
["https://github.com/dotnet/installer"] = 22_436,
["https://github.com/dotnet/roslyn"] = 79_484,
["https://github.com/dotnet/aspnetcore"] = 48_386
};
Console.WriteLine(
C#
this T? nullable,
hasValue = nullable.HasValue;
value = nullable.GetValueOrDefault();
Este método de extensión permite que todos los tipos Nullable<T> se deconstruyan en
una tupla de (bool hasValue, T value) . En el ejemplo siguiente se muestra el código
que usa este método de extensión:
C#
Console.WriteLine(
questionableDateTime = DateTime.Now;
Console.WriteLine(
// Example outputs:
Tipos record
Cuando se declara un tipo de registro con dos o más parámetros posicionales, el
compilador crea un método Deconstruct con un parámetro out para cada parámetro
posicional en la declaración de record . Para obtener más información, vea Sintaxis
posicional para la definición de propiedades y Comportamiento de deconstructores en
registros derivados.
Vea también
Deconstrucción de declaraciones de variables (regla de estilo IDE0042)
Descartes
Tipos de tupla
Excepciones y control de excepciones
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
En este ejemplo, un método prueba a hacer la división entre cero y detecta el error. Sin
el control de excepciones, este programa finalizaría con un error
DivideByZeroException no controlada.
C#
if (y == 0)
return x / y;
double a = 98, b = 0;
double result;
try
catch (DivideByZeroException)
Vea también
SystemException
Palabras clave de C#
throw
try-catch
try-finally
try-catch-finally
Excepciones
Uso de excepciones
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
En C#, los errores del programa en tiempo de ejecución se propagan a través del
programa mediante un mecanismo denominado excepciones. Las excepciones las inicia
el código que encuentra un error y las detecta el código que puede corregir dicho error.
El entorno de ejecución .NET o el código de un programa pueden producir excepciones.
Una vez iniciada, una excepción se propaga hasta la pila de llamadas hasta que
encuentra una instrucción catch para la excepción. Las excepciones no detectadas se
controlan mediante un controlador de excepciones que ofrece el sistema y muestra un
cuadro de diálogo.
Las excepciones están representadas por clases derivadas de Exception. Esta clase
identifica el tipo de excepción y contiene propiedades que tienen los detalles sobre la
excepción. Iniciar una excepción implica crear una instancia de una clase derivada de
excepción, configurar opcionalmente las propiedades de la excepción y luego producir
el objeto con la palabra clave throw . Por ejemplo:
C#
Catch suelen especificar tipos de excepción; si el tipo del bloque catch es el mismo de
C#
try
TestThrow();
System.Console.WriteLine(ex.ToString());
Si la instrucción que inicia una excepción no está en un bloque try , o si el bloque try
que la encierra no tiene un elemento catch coincidente, el entorno de ejecución busca
una instrucción try y bloques catch en el método de llamada. El entorno runtime sigue
hasta la pila de llamadas para buscar un bloque catch compatible. Después de
encontrar el bloque catch y ejecutarlo, el control pasa a la siguiente instrucción
después de dicho bloque catch .
Una instrucción try puede contener más de un bloque catch . Se ejecuta la primera
instrucción catch que pueda controlar la excepción; las instrucciones catch siguientes
se omiten, aunque sean compatibles. Ordene los bloques catch de más específicos (o
más derivados) a menos específicos. Por ejemplo:
C#
using System;
using System.IO;
namespace Exceptions
try
sw.WriteLine("Hello");
Console.WriteLine(ex);
Console.WriteLine(ex);
Console.WriteLine(ex);
Console.WriteLine("Done");
Para que el bloque catch se ejecute, el entorno runtime busca bloques finally . Los
bloques Finally permiten al programador limpiar cualquier estado ambiguo que
pudiera haber quedado tras la anulación de un bloque try o liberar los recursos
externos (como identificadores de gráficos, conexiones de base de datos o flujos de
archivos) sin tener que esperar a que el recolector de elementos no utilizados en el
entorno de ejecución finalice los objetos. Por ejemplo:
C#
try
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
finally
file?.Close();
try
file = fileInfo.OpenWrite();
Console.WriteLine("OpenWrite() succeeded");
catch (IOException)
Console.WriteLine("OpenWrite() failed");
Si WriteByte() ha iniciado una excepción, el código del segundo bloque try que
intente reabrir el archivo generaría un error si no se llama a file.Close() , y el archivo
permanecería bloqueado. Como los bloques finally se ejecutan aunque se inicie una
excepción, el bloque finally del ejemplo anterior permite que el archivo se cierre
correctamente y ayuda a evitar un error.
Si no se encuentra ningún bloque catch compatible en la pila de llamadas después de
iniciar una excepción, sucede una de estas tres acciones:
Los programadores de C# usan un bloque try para separar el código que podría verse
afectado por una excepción. Los bloques catch asociados se usan para controlar las
excepciones resultantes. Un bloque finally contiene código que se ejecuta
independientemente de si se produce una excepción en el bloque try , como la
liberación de recursos asignados en el bloque try . Los bloques try requieren uno o
varios bloques catch asociados, un bloque finally o ambos.
En los ejemplos siguientes se muestra una instrucción try-catch , una instrucción try-
finally y una instrucción try-catch-finally .
C#
try
C#
try
finally
C#
try
finally
// goes here.
Un bloque try sin un bloque catch o finally produce un error del compilador.
Bloques catch
Los bloques catch pueden especificar el tipo de excepción que quiere detectar. La
especificación de tipo se denomina filtro de excepciones. El tipo de excepción se debe
derivar de Exception. En general, no especifique Exception como el filtro de excepciones
a menos que sepa cómo controlar todas las que puedan producirse en el bloque try o
que haya incluido una instrucción throw al final del bloque catch .
Se pueden encadenar juntos varios bloques catch con distintas clases de excepciones.
Los bloques catch se evalúan de arriba abajo en el código, pero solo se ejecuta un
bloque catch para cada excepción que se produce. Se ejecuta el primer bloque catch
que especifica el tipo exacto o una clase base de la excepción producida. Si no hay
ningún bloque catch que especifique una clase de excepciones coincidente, se
selecciona un bloque catch que no tenga ningún tipo, si hay alguno en la instrucción.
Es importante colocar primero los bloques catch con las clases de excepción más
específicas (es decir, las más derivadas).
C#
try
return array[index];
catch (IndexOutOfRangeException e)
C#
try
catch (UnauthorizedAccessException e)
LogError(e);
throw;
También puede especificar filtros de excepción para agregar una expresión booleana a
una cláusula catch. Los filtros de excepción indican que una cláusula catch específica
solo coincide cuando la condición es "true". En el ejemplo siguiente, ambas cláusulas
catch usan la misma clase de excepción, pero se comprueba una condición adicional
para crear un mensaje de error distinto:
C#
try
return array[index];
catch (IndexOutOfRangeException e)
Se puede usar un filtro de excepción que siempre devuelva false para examinar todas
las excepciones, pero no para procesarlas. Un uso habitual es registrar excepciones:
C#
try
string? s = null;
Console.WriteLine(s.Length);
Console.WriteLine($"\tMessage: {e.Message}");
return false;
El método LogException siempre devuelve false ; ninguna cláusula catch que use este
filtro de excepción coincide. La cláusula catch puede ser general, mediante el uso de
System.Exception, y las cláusulas posteriores pueden procesar clases de excepción más
específicas.
Bloques Finally
Los bloques finally permiten limpiar las acciones que se realizan en un bloque try . Si
está presente, el bloque finally se ejecuta en último lugar, después del bloque try y
de cualquier bloque catch coincidente. Un bloque finally siempre se ejecuta,
independientemente de si se produce una excepción o si se encuentra un bloque catch
que coincida con el tipo de excepción.
Los bloques finally pueden usarse para liberar recursos como secuencias de archivo,
conexiones de base de datos y controladores de gráficos sin necesidad de esperar a que
el recolector de elementos no utilizados en tiempo de ejecución finalice los objetos.
Para obtener más información, vea using (instrucción).
En el ejemplo siguiente, el bloque finally se usa para cerrar un archivo que se abre en
el bloque try . Observe que se comprueba el estado del identificador de archivos antes
de cerrar el archivo. Si el bloque try no puede abrir el archivo, el manipulador de
archivos sigue teniendo el valor null , y el bloque finally no intenta cerrarlo. En lugar
de eso, si el archivo se abre correctamente en el bloque try , el bloque finally cierra el
archivo abierto.
C#
try
file = fileinfo.OpenWrite();
file.WriteByte(0xF);
finally
file?.Close();
Consulte también
Referencia de C#
try-catch
try-finally
try-catch-finally
using (instrucción)
Creación y producción de excepciones
Artículo • 15/02/2023 • Tiempo de lectura: 4 minutos
C#
Se realiza una llamada inadecuada a un objeto, en función del estado del objeto.
Un ejemplo podría ser intentar escribir en un archivo de solo lectura. En los casos
en los que un estado de objeto no permite una operación, genere una instancia de
InvalidOperationException o un objeto con base en una derivación de esta clase. El
código siguiente es un ejemplo de un método que genera un objeto
InvalidOperationException:
C#
if (!logFile.CanWrite)
C#
try
return array[index];
catch (IndexOutOfRangeException e)
7 Nota
Las excepciones contienen una propiedad denominada StackTrace. Esta cadena contiene
el nombre de los métodos de la pila de llamadas actual, junto con el nombre de archivo
y el número de la línea en la que se ha producido la excepción para cada método.
Common Language Runtime (CLR) crea automáticamente un objeto StackTrace desde el
punto de la instrucción throw , de manera que todas las excepciones se deben producir
desde el punto en el que debe comenzar el seguimiento de la pila.
Todas las excepciones contienen una propiedad denominada Message. Esta cadena
debe establecerse para que explique el motivo de la excepción. No se debe colocar
información confidencial en materia de seguridad en el texto del mensaje. Además de
Message, ArgumentException contiene una propiedad denominada ParamName que
debe establecerse en el nombre del argumento que ha provocado que se genere la
excepción. En un establecedor de propiedades, ParamName debe establecerse en
value .
Los métodos públicos y protegidos generan excepciones siempre que no puedan
finalizar sus funciones previstas. La clase de excepciones generada es la excepción más
específica disponible que se ajuste a las condiciones de error. Estas excepciones se
deben documentar como parte de la funcionalidad de la clase, y las clases derivadas o
actualizaciones de la clase original deben mantener el mismo comportamiento para la
compatibilidad con versiones anteriores.
C#
[Serializable]
Vea también
Jerarquía de excepciones
Excepciones generadas por el
compilador
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Excepción Descripción
ArithmeticException Una clase base para las excepciones que se producen durante las
operaciones aritméticas, como DivideByZeroException y
OverflowException.
Vea también
try-catch
try-finally
try-catch-finally
Convenciones y reglas de nomenclatura
de identificadores de C#
Artículo • 29/11/2022 • Tiempo de lectura: 2 minutos
Reglas de nomenclatura
Los identificadores válidos deben seguir estas reglas:
Convenciones de nomenclatura
Además de las reglas, hay muchas convenciones de nomenclatura de identificadores
que se utilizan en las API de .NET. Por convención, los programas de C# usan
PascalCase para nombres de tipo, espacios de nombres y todos los miembros públicos.
Además, son comunes las convenciones siguientes:
Consulte también
Guía de programación de C#
Referencia de C#
Clases
Tipos de estructura
Espacios de nombres
Interfaces
Delegados
Convenciones de código de C#
Artículo • 03/03/2023 • Tiempo de lectura: 13 minutos
" Crean una apariencia coherente en el código, para que los lectores puedan
centrarse en el contenido, no en el diseño.
" Permiten a los lectores comprender el código más rápidamente al hacer
suposiciones basadas en la experiencia anterior.
" Facilitan la copia, el cambio y el mantenimiento del código.
" Muestran los procedimientos recomendados de C#.
) Importante
Convenciones de nomenclatura
Hay varias convenciones de nomenclatura que se deben tener en cuenta al escribir
código de C#.
llamadas externos.
Pascal case
Use la grafía Pascal ("PascalCasing") al asignar un nombre a class , record o struct .
C#
C#
string Street,
string City,
string StateOrProvince,
string ZipCode);
C#
C#
C#
// An init-only property
// An event
// Method
// Local function
// ...
Al escribir registros posicionales, use la grafía Pascal para los parámetros, ya que son las
propiedades públicas del registro.
C#
string Street,
string City,
string StateOrProvince,
string ZipCode);
Para más información sobre los registros posicionales, consulte Sintaxis posicional para
la definición de propiedades.
Grafía Camel
Use la grafía Camel ("camelCasing") al asignar nombres a campos private o internal , y
prefijos con _ .
C#
Sugerencia
Al trabajar con campos static que sean private o internal , use el prefijo s_ y, para el
subproceso estático, use t_ .
C#
[ThreadStatic]
C#
Para obtener más información sobre las convenciones de nomenclatura de C#, vea Estilo
de codificación de C# .
C#
PerformanceCounterCategory();
Convenciones de diseño
Un buen diseño utiliza un formato que destaque la estructura del código y haga que el
código sea más fácil de leer. Las muestras y ejemplos de Microsoft cumplen las
convenciones siguientes:
Utilice paréntesis para que las cláusulas de una expresión sean evidentes, como se
muestra en el código siguiente.
C#
C#
using Azure;
namespace CoolStuff.AwesomeFeature
C#
namespace CoolStuff.AwesomeFeature
using Azure;
Consola
- error CS0246: The type or namespace name 'WaitUntil' could not be found
(are you missing a using directive or an assembly reference?)
- error CS0103: The name 'WaitUntil' does not exist in the current context
C#
namespace CoolStuff.Azure
CoolStuff.AwesomeFeature.Azure
CoolStuff.Azure
Azure
C#
namespace CoolStuff.AwesomeFeature
using global::Azure;
C#
// the query.
Asegúrese de que todos los miembros públicos tengan los comentarios XML
necesarios que proporcionan descripciones adecuadas sobre su comportamiento.
Convenciones de lenguaje
En las secciones siguientes se describen las prácticas que sigue el equipo C# para
preparar las muestras y ejemplos de código.
Tipo de datos String
Use interpolación de cadenas para concatenar cadenas cortas, como se muestra en
el código siguiente.
C#
C#
var phrase =
"lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala";
manyPhrases.Append(phrase);
//Console.WriteLine("tra" + manyPhrases);
C#
No use var cuando el tipo no sea evidente desde el lado derecho de la asignación.
No asuma que el tipo está claro a partir de un nombre de método. Se considera
que un tipo de variable es claro si es un operador new o una conversión explícita.
C#
C#
Console.WriteLine(inputInt);
Evite el uso de var en lugar de dynamic. Use dynamic cuando desee la inferencia
de tipos en tiempo de ejecución. Para obtener más información, vea Uso de tipo
dinámico (Guía de programación de C#).
Use tipos implícitos para determinar el tipo de la variable de bucle en bucles for.
C#
var phrase =
"lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala";
manyPhrases.Append(phrase);
//Console.WriteLine("tra" + manyPhrases);
C#
if (ch == 'h')
Console.Write("H");
else
Console.Write(ch);
Console.WriteLine();
7 Nota
Matrices
Utilice sintaxis concisa para inicializar las matrices en la línea de declaración. En el
siguiente ejemplo, observe que no puede utilizar var en lugar de string[] .
C#
C#
Delegados
Use Func<> y Action<> en lugar de definir tipos de delegado. En una clase, defina el
método delegado.
C#
C#
Si crea instancias de un tipo de delegado, utilice la sintaxis concisa. En una clase, defina
el tipo de delegado y un método que tenga una firma coincidente.
C#
Cree una instancia del tipo de delegado y llámela. La siguiente declaración muestra la
sintaxis condensada.
C#
exampleDel2("Hey");
C#
exampleDel1("Hey");
C#
try
return array[index];
throw;
C#
try
finally
if (font1 != null)
((IDisposable)font1).Dispose();
C#
C#
Operadores && y ||
Para evitar excepciones y aumentar el rendimiento omitiendo las comparaciones
innecesarias, use && en lugar de & y || en lugar de | cuando realice comparaciones,
como se muestra en el ejemplo siguiente.
C#
else
Operador new
Use una de las formas concisas de creación de instancias de objeto, tal como se
muestra en las declaraciones siguientes. En el segundo ejemplo se muestra la
sintaxis que está disponible a partir de C# 9.
C#
C#
C#
C#
C#
instance4.Name = "Desktop";
instance4.ID = 37414;
instance4.Location = "Redmond";
instance4.Age = 2.3;
Control de eventos
Si va a definir un controlador de eventos que no es necesario quitar más tarde, utilice
una expresión lambda.
C#
public Form2()
MessageBox.Show(
((MouseEventArgs)e).Location.ToString());
};
C#
public Form1()
MessageBox.Show(((MouseEventArgs)e).Location.ToString());
Miembros estáticos
Llame a miembros estáticos con el nombre de clase: ClassName.StaticMember. Esta
práctica hace que el código sea más legible al clarificar el acceso estático. No califique
un miembro estático definido en una clase base con el nombre de una clase derivada.
Mientras el código se compila, su legibilidad se presta a confusión, y puede
interrumpirse en el futuro si se agrega a un miembro estático con el mismo nombre a la
clase derivada.
Consultas LINQ
Utilice nombres descriptivos para las variables de consulta. En el ejemplo siguiente,
se utiliza seattleCustomers para los clientes que se encuentran en Seattle.
C#
select customer.Name;
Utilice alias para asegurarse de que los nombres de propiedad de tipos anónimos
se escriben correctamente con mayúscula o minúscula, usando para ello la grafía
Pascal.
C#
var localDistributors =
C#
var localDistributors2 =
C#
select customer.Name;
Alinee las cláusulas de consulta bajo la cláusula from, como se muestra en los
ejemplos anteriores.
Use cláusulas where antes de otras cláusulas de consulta para asegurarse de que
las cláusulas de consulta posteriores operan en un conjunto de datos reducido y
filtrado.
C#
orderby customer.Name
select customer;
Use varias cláusulas from en lugar de una cláusula join para obtener acceso a
colecciones internas. Por ejemplo, una colección de objetos Student podría
contener cada uno un conjunto de resultados de exámenes. Cuando se ejecuta la
siguiente consulta, devuelve cada resultado superior a 90, además del apellido del
alumno que recibió la puntuación.
C#
Seguridad
Siga las instrucciones de Instrucciones de codificación segura.
Consulte también
Instrucciones de codificación en el entorno de ejecución de .NET
Convenciones de código de Visual Basic
Instrucciones de codificación segura
Procedimiento para mostrar
argumentos de la línea de comandos
Artículo • 28/11/2022 • Tiempo de lectura: 2 minutos
executable.exe a b c "a"
"b"
"c"
"two"
"three"
7 Nota
Ejemplo
En este ejemplo se muestran los argumentos de la línea de comandos pasados a una
aplicación de la línea de comandos. La salida que se muestra corresponde a la primera
entrada de la tabla anterior.
C#
Console.WriteLine($"Arg[{i}] = [{args[i]}]");
parameter count = 3
Arg[0] = [a]
Arg[1] = [b]
Arg[2] = [c]
*/
Consulte también
Introducción a System.CommandLine
Tutorial: Introducción a System.CommandLine
Exploración de la programación
orientada a objetos con clases y objetos
Artículo • 28/11/2022 • Tiempo de lectura: 10 minutos
En este tutorial, creará una aplicación de consola y conocerá las características básicas
orientadas a objetos que forman parte del lenguaje C#.
Requisitos previos
Se recomienda tener Visual Studio para Windows o Mac. Puede descargar una
versión gratuita desde la página de descargas de Visual Studio . Visual Studio
incluye el SDK de .NET.
También se puede usar el editor de Visual Studio Code . Deberá instalar el SDK
de .NET más reciente por separado.
Si prefiere usar otro editor, deberá instalar el SDK de .NET más reciente.
C#
Console.WriteLine("Hello, World!");
En este tutorial, se van a crear tipos nuevos que representan una cuenta bancaria.
Normalmente los desarrolladores definen cada clase en un archivo de texto diferente.
De esta forma, la tarea de administración resulta más sencilla a medida que aumenta el
tamaño del programa. Cree un archivo denominado BankAccount.cs en el directorio
Classes.
1. Tiene un número de diez dígitos que identifica la cuenta bancaria de forma única.
2. Tiene una cadena que almacena el nombre o los nombres de los propietarios.
3. Se puede consultar el saldo.
4. Acepta depósitos.
5. Acepta reintegros.
6. El saldo inicial debe ser positivo.
7. Los reintegros no pueden generar un saldo negativo.
C#
namespace Classes;
public class BankAccount define la clase o el tipo que quiere crear. Todo lo que se
Para crear un objeto de tipo BankAccount , es necesario definir un constructor que asigne
esos valores. Un constructor es un miembro que tiene el mismo nombre que la clase. Se
usa para inicializar los objetos de ese tipo de clase. Agregue el siguiente constructor al
tipo BankAccount . Coloque el siguiente código encima de la declaración de MakeDeposit .
C#
this.Owner = name;
this.Balance = initialBalance;
En el código anterior se identifican las propiedades del objeto que se está construyendo
mediante la inclusión del calificador this . Ese calificador suele ser opcional y se omite.
También podría haber escrito lo siguiente:
C#
Owner = name;
Balance = initialBalance;
El calificador this solo es necesario cuando una variable o un parámetro local tiene el
mismo nombre que el campo o la propiedad. El calificador this se omite en el resto de
este artículo, a menos que sea necesario.
A los constructores se les llama cuando se crea un objeto mediante new. Reemplace la
línea Console.WriteLine("Hello World!"); de Program.cs por la siguiente línea
(reemplace <name> por su nombre):
C#
using Classes;
Vamos a ejecutar lo que se ha creado hasta ahora. Si usa Visual Studio, seleccione Iniciar
sin depurar en el menú Depurar. Si va a usar una línea de comandos, escriba dotnet
run en el directorio en el que ha creado el proyecto.
de empezar es con un número de diez dígitos. Increméntelo cuando cree cada cuenta.
Por último, almacene el número de cuenta actual cuando se construya un objeto.
C#
que solo se puede acceder a él con el código incluido en la clase BankAccount . Es una
forma de separar las responsabilidades públicas (como tener un número de cuenta) de
la implementación privada (cómo se generan los números de cuenta). También es
static , lo que significa que lo comparten todos los objetos BankAccount . El valor de
una variable no estática es único para cada instancia del objeto BankAccount . Agregue
las dos líneas siguientes al constructor para asignar el número de cuenta: Colóquelas
después de la línea donde pone this.Balance = initialBalance :
C#
this.Number = accountNumberSeed.ToString();
accountNumberSeed++;
C#
namespace Classes;
Amount = amount;
Date = date;
Notes = note;
C#
get
decimal balance = 0;
balance += item.Amount;
return balance;
Las reglas presentan el concepto de las excepciones. La forma habitual de indicar que un
método no puede completar su trabajo correctamente consiste en generar una
excepción. El tipo de excepción y el mensaje asociado a ella describen el error. En este
caso, el método MakeDeposit genera una excepción si el importe del depósito no es
mayor que 0. El método MakeWithdrawal genera una excepción si el importe del
reintegro no es mayor que 0 o si el procesamiento de la operación tiene como resultado
un saldo negativo: Agregue el código siguiente después de la declaración de la lista
allTransactions :
C#
if (amount <= 0)
allTransactions.Add(deposit);
}
if (amount <= 0)
allTransactions.Add(withdrawal);
El constructor debe obtener un cambio para que agregue una transacción inicial, en
lugar de actualizar el saldo directamente. Puesto que ya escribió el método
MakeDeposit , llámelo desde el constructor. El constructor terminado debe tener este
aspecto:
C#
Number = accountNumberSeed.ToString();
accountNumberSeed++;
Owner = name;
DateTime.Now es una propiedad que devuelve la fecha y hora actuales. Para probar este
código, agregue algunos depósitos y reintegros en el método Main , siguiendo el código
con el que se crea un elemento BankAccount :
C#
Console.WriteLine(account.Balance);
Console.WriteLine(account.Balance);
Después, compruebe si detecta las condiciones de error intentando crear una cuenta
con un saldo negativo. Agregue el código siguiente después del código anterior que
acaba de agregar:
C#
BankAccount invalidAccount;
try
catch (ArgumentOutOfRangeException e)
Console.WriteLine(e.ToString());
return;
Use las instrucciones try y catch para marcar un bloque de código que puede generar
excepciones y para detectar los errores que se esperan. Puede usar la misma técnica
para probar el código que genera una excepción para un saldo negativo. Agregue el
código siguiente antes de la declaración de invalidAccount en el método Main :
C#
try
catch (InvalidOperationException e)
Console.WriteLine(e.ToString());
C#
decimal balance = 0;
report.AppendLine("Date\t\tAmount\tBalance\tNote");
balance += item.Amount;
report.AppendLine($"
{item.Date.ToShortDateString()}\t{item.Amount}\t{balance}\t{item.Notes}");
return report.ToString();
En el historial se usa la clase StringBuilder para dar formato a una cadena que contiene
una línea para cada transacción. Se ha visto anteriormente en estos tutoriales el código
utilizado para dar formato a una cadena. Un carácter nuevo es \t . Inserta una pestaña
para dar formato a la salida.
C#
Console.WriteLine(account.GetAccountHistory());
Pasos siguientes
Si se ha quedado bloqueado, puede consultar el origen de este tutorial en el repositorio
de GitHub .
Instrucciones de selección
Instrucciones de iteración
Programación orientada a objetos (C#)
Artículo • 28/11/2022 • Tiempo de lectura: 12 minutos
el tutorial anterior.
Una cuenta que devenga intereses que genera beneficios al final de cada mes.
Una línea de crédito que puede tener un saldo negativo, pero cuando sea así, se
producirá un cargo por intereses cada mes.
Una cuenta de tarjeta de regalo de prepago que comienza con un único depósito
y solo se puede liquidar. Se puede recargar una vez al principio de cada mes.
Todas estas cuentas diferentes son similares a la clase BankAccount definida en el tutorial
anterior. Podría copiar ese código, cambiar el nombre de las clases y realizar
modificaciones. Esa técnica funcionaría a corto plazo, pero a la larga supondría más
trabajo. Cualquier cambio se copiará en todas las clases afectadas.
En su lugar, puede crear nuevos tipos de cuenta bancaria que hereden métodos y datos
de la clase BankAccount creada en el tutorial anterior. Estas clases nuevas pueden
extender la clase BankAccount con el comportamiento específico necesario para cada
tipo:
C#
Cuando cree las clases como se muestra en el ejemplo anterior, observará que ninguna
de las clases derivadas se compila. La inicialización de un objeto es responsabilidad de
un constructor. Un constructor de clase derivada debe inicializar la clase derivada y
proporcionar instrucciones sobre cómo inicializar el objeto de la clase base incluido en
la clase derivada. Normalmente, se produce una inicialización correcta sin ningún
código adicional. La clase BankAccount declara un constructor público con la siguiente
firma:
C#
C#
Los parámetros de este nuevo constructor coinciden con el tipo de parámetro y los
nombres del constructor de clase base. Utilice la sintaxis de : base() para indicar una
llamada a un constructor de clase base. Algunas clases definen varios constructores, y
esta sintaxis le permite elegir el constructor de clase base al que llama. Una vez que
haya actualizado los constructores, puede desarrollar el código para cada una de las
clases derivadas. Los requisitos para las clases nuevas se pueden indicar de la siguiente
manera:
Puede ver que los tres tipos de cuenta tienen una acción que tiene lugar al final de cada
mes. Sin embargo, cada tipo de cuenta realiza diferentes tareas. Utiliza el polimorfismo
para implementar este código. Cree un método virtual único en la clase BankAccount :
C#
El código anterior muestra cómo se usa la palabra clave virtual para declarar un
método en la clase base para el que una clase derivada puede proporcionar una
implementación diferente. Un método virtual es un método en el que cualquier clase
derivada puede optar por volver a implementar. Las clases derivadas usan la palabra
clave override para definir la nueva implementación. Normalmente, se hace referencia a
este proceso como "reemplazar la implementación de la clase base". La palabra clave
virtual especifica que las clases derivadas pueden invalidar el comportamiento.
También puede declarar métodos abstract en los que las clases derivadas deben
reemplazar el comportamiento. La clase base no proporciona una implementación para
un método abstract . A continuación, debe definir la implementación de dos de las
nuevas clases que ha creado. Empiece por InterestEarningAccount :
C#
C#
if (Balance < 0)
C#
C#
if (_monthlyDeposit != 0)
C#
giftCard.PerformMonthEndTransactions();
Console.WriteLine(giftCard.GetAccountHistory());
savings.PerformMonthEndTransactions();
Console.WriteLine(savings.GetAccountHistory());
Verifique los resultados. Ahora, agregue un conjunto similar de código de prueba para
LineOfCreditAccount :
lineOfCredit.PerformMonthEndTransactions();
Console.WriteLine(lineOfCredit.GetAccountHistory());
Consola
7 Nota
La salida real incluye la ruta de acceso completa a la carpeta con el proyecto. Los
nombres de carpeta se omitieron para ser más breves. Además, dependiendo del
formato del código, los números de línea pueden ser ligeramente diferentes.
Este código produce un error porque BankAccount supone que el saldo inicial debe ser
mayor que 0. Otra suposición incorporada en la clase BankAccount es que el saldo no
puede entrar en cifras negativas. Lo que sucede que es se rechazan las retiradas que
provocan un descubierto en la cuenta. Ambas suposiciones deben cambiar. La línea de
la cuenta de crédito comienza en 0, y generalmente tendrá un saldo negativo. Además,
si un cliente retira demasiado dinero, generará un cargo. La transacción se acepta, solo
que cuesta más. La primera regla se puede implementar agregando un argumento
opcional al constructor BankAccount que especifica el saldo mínimo. El valor
predeterminado es 0 . La segunda regla requiere un mecanismo que permita que las
clases derivadas modifiquen el algoritmo predeterminado. En cierto sentido, la clase
base "pregunta" al tipo derivado qué debe ocurrir cuando hay un descubierto. El
comportamiento predeterminado es rechazar la transacción generando una excepción.
Comencemos agregando un segundo constructor que incluya un parámetro
minimumBalance opcional. Este nuevo constructor se ocupa de todas las acciones que
realiza el constructor existente. Además, establece la propiedad del saldo mínimo.
Puede copiar el cuerpo del constructor existente, pero eso significa que dos ubicaciones
cambiarán en el futuro. Lo que puede hacer es usar un encadenamiento de constructores
para que un constructor llame a otro. En el código siguiente se muestran los dos
constructores y el nuevo campo adicional:
C#
Number = s_accountNumberSeed.ToString();
s_accountNumberSeed++;
Owner = name;
_minimumBalance = minimumBalance;
if (initialBalance > 0)
Esta implementación solo llama a MakeDeposit si el saldo inicial es mayor que 0 . Esto
conserva la regla de que los depósitos deben ser positivos, pero permite que la cuenta
de crédito se abra con un saldo de 0 .
Ahora que la clase BankAccount tiene un campo de solo lectura para el saldo mínimo, el
último cambio es modificar la codificación rígida 0 a minimumBalance en el método
MakeWithdrawal :
C#
if (Balance - amount < minimumBalance)
C#
C#
if (amount <= 0)
allTransactions.Add(withdrawal);
C#
if (amount <= 0)
_allTransactions.Add(withdrawal);
if (overdraftTransaction != null)
_allTransactions.Add(overdraftTransaction);
if (isOverdrawn)
else
return default;
}
El método agregado es protected , lo que significa que solo se puede llamar desde
clases derivadas. Esa declaración impide que otros clientes llamen al método. También
es virtual para que las clases derivadas puedan cambiar el comportamiento. El tipo de
valor devuelto es Transaction? . La anotación ? indica que el método puede devolver
null . Agregue la siguiente implementación en LineOfCreditAccount para cobrar una
cuota cuando se supere el límite de retirada:
C#
isOverdrawn
: default;
C#
lineOfCredit.PerformMonthEndTransactions();
Console.WriteLine(lineOfCredit.GetAccountHistory());
Resumen
Si se ha quedado bloqueado, puede consultar el origen de este tutorial en el repositorio
de GitHub .
En este tutorial se han mostrado muchas de las técnicas que se usan en la programación
orientada a objetos:
Usó la abstracción cuando definió clases para cada uno de los distintos tipos de
cuenta. Esas clases describían el comportamiento de ese tipo de cuenta.
Usó la encapsulación cuando mantuvo muchos detalles private en cada clase.
Usó la herencia cuando aprovechó la implementación ya creada en la clase
BankAccount para guardar el código.
Usó el polimorfismo cuando creó métodos virtual que las clases derivadas
podrían reemplazar para crear un comportamiento específico para ese tipo de
cuenta.
Herencia en C# y .NET
Artículo • 04/02/2023 • Tiempo de lectura: 27 minutos
Requisitos previos
Se recomienda tener Visual Studio para Windows o Mac. Puede descargar una
versión gratuita desde la página de descargas de Visual Studio . Visual Studio
incluye el SDK de .NET.
También se puede usar el editor de Visual Studio Code . Deberá instalar el SDK
de .NET más reciente por separado.
Si prefiere usar otro editor, deberá instalar el SDK de .NET más reciente.
2. Escriba el comando dotnet new console en el símbolo del sistema para crear un
nuevo proyecto de .NET Core.
C# y .NET solo admiten herencia única. Es decir, una clase solo puede heredar de una
clase única. Sin embargo, la herencia es transitiva, lo que le permite definir una jerarquía
de herencia para un conjunto de tipos. En otras palabras, el tipo D puede heredar del
tipo C , que hereda del tipo B , que hereda del tipo de clase base A . Dado que la
herencia es transitiva, los miembros de tipo A están disponibles para el tipo D .
No todos los miembros de una clase base los heredan las clases derivadas. Los
siguientes miembros no se heredan:
Constructores de instancias, a los que se llama para crear una nueva instancia de la
clase. Cada clase debe definir sus propios constructores.
Si bien las clases derivadas heredan todos los demás miembros de una clase base, que
dichos miembros estén o no visibles depende de su accesibilidad. La accesibilidad del
miembro afecta a su visibilidad en las clases derivadas del modo siguiente:
Los miembros privados solo son visible en las clases derivadas que están anidadas
en su clase base. De lo contrario, no son visibles en las clases derivadas. En el
ejemplo siguiente, A.B es una clase anidada que se deriva de A , y C se deriva de
A . El campo privado A._value es visible en A.B. Pero si quita los comentarios del
C#
public class A
public class B : A
return _value;
public class C : A
// {
// return _value;
// }
Console.WriteLine(b.GetValue());
// 10
Los miembros internos solo son visibles en las clases derivadas que se encuentran
en el mismo ensamblado que la clase base. No son visibles en las clases derivadas
ubicadas en un ensamblado diferente al de la clase base.
Los miembros públicos son visibles en las clases derivadas y forman parte de la
interfaz pública de dichas clases. Los miembros públicos heredados se pueden
llamar como si se definieran en la clase derivada. En el ejemplo siguiente, la clase
A define un método denominado Method1 y la clase B hereda de la clase A . El
ejemplo llama a Method1 como si fuera un método de instancia en B .
C#
public class A
// Method implementation.
}
public class B : A
{ }
B b = new ();
b.Method1();
Las clases derivadas pueden también invalidar los miembros heredados al proporcionar
una implementación alternativa. Para poder invalidar un miembro, el miembro de la
clase base debe marcarse con la palabra clave virtual. De forma predeterminada, los
miembros de clase base no están marcados con virtual y no se pueden invalidar. Al
intentar invalidar un miembro no virtual, como se muestra en el ejemplo siguiente, se
genera el error del compilador CS0506: "<el miembro> no puede invalidar el miembro
<> heredado porque no está marcado como virtual, abstracto o invalidado".
C#
public class A
// Do something.
public class B : A
// Do something else.
En algunos casos, una clase derivada debe invalidar la implementación de la clase base.
Los miembros de clase base marcados con la palabra clave abstract requieren que las
clases derivadas los invaliden. Al intentar compilar el ejemplo siguiente, se genera el
error de compilador CS0534, "<class> no implementa el miembro abstracto heredado
<member>", porque la clase B no proporciona ninguna implementación para
A.Method1 .
C#
// Do something.
La herencia solo se aplica a clases e interfaces. Other type categories (structs, delegates,
and enums) do not support inheritance. Debido a estas reglas, al intentar compilar
código como en el ejemplo siguiente se produce el error del compilador CS0527: "El
tipo 'ValueType' en la lista de interfaz no es una interfaz". El mensaje de error indica que,
aunque se pueden definir las interfaces que implementa una estructura, no se admite la
herencia.
C#
Herencia implícita
Aparte de los tipos de los que puedan heredar mediante herencia única, todos los tipos
del sistema de tipos .NET heredan implícitamente de Object o de un tipo derivado de
este. La funcionalidad común de Object está disponible para cualquier tipo.
Para ver lo que significa la herencia implícita, vamos a definir una nueva clase,
SimpleClass , que es simplemente una definición de clase vacía:
C#
{ }
Después, se puede usar la reflexión (que permite inspeccionar los metadatos de un tipo
para obtener información sobre ese tipo) con el fin de obtener una lista de los
miembros que pertenecen al tipo SimpleClass . Aunque no se ha definido ningún
miembro en la clase SimpleClass , la salida del ejemplo indica que en realidad tiene
nueve miembros. Uno de ellos es un constructor sin parámetros (o predeterminado) que
el compilador de C# proporciona de manera automática para el tipo SimpleClass . Los
ocho restantes son miembros de Object, el tipo del que heredan implícitamente a la
larga todas las clases e interfaces del sistema de tipo .NET.
C#
using System.Reflection;
Type t = typeof(SimpleClass);
BindingFlags.NonPublic |
BindingFlags.FlattenHierarchy;
if (method != null)
if (method.IsPublic)
else if (method.IsPrivate)
else if (method.IsFamily)
else if (method.IsAssembly)
else if (method.IsFamilyOrAssembly)
if (method.IsStatic)
Console.WriteLine(output);
La herencia implícita desde la clase Object permite que estos métodos estén disponibles
para la clase SimpleClass :
El método público GetHashCode , que calcula un valor que permite que una
instancia del tipo se use en colecciones con hash.
El método público GetType , que devuelve un objeto Type que representa el tipo
SimpleClass .
C#
EmptyClass sc = new();
Console.WriteLine(sc.ToString());
// EmptyClass
clase Object
7 Nota
Una clase o struct puede implementar una o varias interfaces. Aunque a menudo la
implementación se presenta como una solución alternativa para la herencia única o
como una forma de usar la herencia con structs, su finalidad es expresar una
relación diferente (una relación "can do") entre una interfaz y su tipo de
implementación que la herencia. Una interfaz define un subconjunto de
funcionalidad (por ejemplo, la posibilidad de probar la igualdad, comparar u
ordenar objetos o de admitir análisis y formato con referencia cultural) que la
interfaz pone a disposición de sus tipos de implementación.
Tenga en cuenta que "is a" también expresa la relación entre un tipo y una instancia
específica de ese tipo. En el ejemplo siguiente, Automobile es una clase que tiene tres
propiedades de solo lectura exclusivas: Make , el fabricante del automóvil; Model , el tipo
de automóvil; y Year , el año de fabricación. La clase Automobile también tiene un
constructor cuyos argumentos se asignan a los valores de propiedad, y reemplaza al
método Object.ToString para crear una cadena que identifica de forma única la instancia
de Automobile en lugar de la clase Automobile .
C#
if (make == null)
else if (string.IsNullOrWhiteSpace(make))
Make = make;
if (model == null)
else if (string.IsNullOrWhiteSpace(model))
Model = model;
Year = year;
using System;
Console.WriteLine(packard);
Una relación "is a" basada en la herencia se aplica mejor a una clase base y a clases
derivadas que agregan miembros adicionales a la clase base o que requieren
funcionalidad adicional que no está presente en la clase base.
Para maximizar la reutilización del código y crear una jerarquía de herencia lógica e
intuitiva, asegúrese de incluir en la clase Publication solo los datos y la
funcionalidad común a todas o a la mayoría de las publicaciones. Así, las clases
derivadas implementan miembros que son únicos para una clase determinada de
publicación que representan.
Hasta qué punto extender la jerarquía de clases. ¿Quiere desarrollar una jerarquía
de tres o más clases, en lugar de simplemente una clase base y una o más clases
derivadas? Por ejemplo, Publication podría ser una clase base de Periodical ,
que, a su vez, es una clase base de Magazine , Journal y Newspaper .
Si tiene sentido crear instancias de la clase base. Si no, se debe aplicar la palabra
clave abstract a la clase. De lo contrario, se puede crear una instancia de la clase
Publication mediante una llamada a su constructor de clase. Si se intenta crear
una instancia de una clase marcada con la palabra clave abstract mediante una
llamada directa a su constructor de clase, el compilador de C# genera el
error CS0144, "No se puede crear una instancia de la clase o interfaz abstracta". Si
se intenta crear una instancia de la clase mediante reflexión, el método de
reflexión produce una excepción MemberAccessException.
En el ejemplo siguiente se muestra el código fuente para la clase Publication , así como
una enumeración PublicationType que devuelve la propiedad
Publication.PublicationType . Además de los miembros que hereda de Object, la clase
Publication define los siguientes miembros únicos e invalidaciones de miembros:
C#
if (string.IsNullOrWhiteSpace(publisher))
Publisher = publisher;
if (string.IsNullOrWhiteSpace(title))
Title = title;
Type = type;
set
if (value <= 0)
_totalPages = value;
if (!_published)
return "NYP";
else
return _datePublished.ToString("d");
_published = true;
_datePublished = datePublished;
if (string.IsNullOrWhiteSpace(copyrightName))
CopyrightName = copyrightName;
CopyrightDate = copyrightDate;
Un constructor
C#
PublicationType.Book);
Title es una propiedad String de solo lectura cuyo valor se suministra mediante la
Pages es una propiedad Int32 de solo lectura que indica cuántas páginas en total
tiene la publicación. El valor se almacena en un campo privado denominado
totalPages . Debe ser un número positivo o se inicia una excepción
ArgumentOutOfRangeException.
La clase Book .
La clase Book representa un libro como un tipo especializado de publicación. En el
ejemplo siguiente se muestra el código fuente de la clase Book .
C#
using System;
{ }
//
if (!string.IsNullOrEmpty(isbn))
ISBN = isbn;
Author = author;
if (price < 0)
if (currency.Length != 3)
Currency = currency;
return oldValue;
return false;
else
Además de los miembros que hereda de Publication , la clase Book define los
siguientes miembros únicos e invalidaciones de miembros:
Dos constructores
Los dos constructores Book comparten tres parámetros comunes. Dos, title y
publisher, corresponden a los parámetros del constructor Publication . La tercera
es author, que se almacena para una propiedad Author pública inmutable. Un
constructor incluye un parámetro isbn, que se almacena en la propiedad
automática ISBN .
El primer constructor usa esta palabra clave para llamar al otro constructor. El
encadenamiento de constructores es un patrón común en la definición de
constructores. Los constructores con menos parámetros proporcionan valores
predeterminados al llamar al constructor con el mayor número de parámetros.
El segundo constructor usa la palabra clave base para pasar el título y el nombre
del editor al constructor de clase base. Si no realiza una llamada explícita a un
constructor de clase base en el código fuente, el compilador de C# proporciona
automáticamente una llamada al constructor sin parámetros o predeterminado de
la clase base.
Una propiedad ISBN de solo lectura, que devuelve el ISBN (International Standard
Book Number) del objeto Book , un número exclusivo de 10 y 13 caracteres. El ISBN
se proporciona como argumento para uno de los constructores Book . El ISBN se
almacena en un campo de respaldo privado, generado automáticamente por el
compilador.
Una propiedad Author de solo lectura. El nombre del autor se proporciona como
argumento para ambos constructores Book y se almacena en la propiedad.
Dos propiedades relacionadas con el precio de solo lectura, Price y Currency . Sus
valores se proporcionan como argumentos en una llamada al método SetPrice . La
propiedad Currency es el símbolo de moneda ISO de tres dígitos (por ejemplo,
USD para el dólar estadounidense). Los símbolos de moneda ISO se pueden
recuperar de la propiedad ISOCurrencySymbol. Ambas propiedades son de solo
lectura desde ubicaciones externas, pero se pueden establecer mediante código en
la clase Book .
C#
ShowPublicationInfo(book);
ShowPublicationInfo(book);
$"{((Publication)book).Equals(book2)}");
Console.WriteLine($"{pub.Title}, " +
// The Tempest and The Tempest are the same publication: False
Por ejemplo, cada forma geométrica bidimensional cerrada incluye dos propiedades:
área, la extensión interna de la forma; y perímetro, o la distancia a lo largo de los bordes
de la forma. La manera en que se calculan estas propiedades, sin embargo, depende
completamente de la forma específica. La fórmula para calcular el perímetro (o la
circunferencia) de un círculo, por ejemplo, es diferente a la de un cuadrado. La clase
Shape es una clase abstract con métodos abstract . Eso indica que las clases derivadas
En el ejemplo siguiente se define una clase base abstracta denominada Shape que
define dos propiedades: Area y Perimeter . Además de marcar la clase con la palabra
clave abstract, cada miembro de instancia también se marca con la palabra clave
abstract. En este caso, Shape también invalida el método Object.ToString para devolver
el nombre del tipo, en lugar de su nombre completo. Y define dos miembros estáticos,
GetArea y GetPerimeter , que permiten que los llamadores recuperen fácilmente el área
C#
Después, se pueden derivar algunas clases de Shape que representan formas concretas.
El ejemplo siguiente define tres clases Square , Rectangle y Circle . Cada una usa una
fórmula única para esa forma en particular para calcular el área y el perímetro. Algunas
de las clases derivadas también definen propiedades, como Rectangle.Diagonal y
Circle.Diameter , que son únicas para la forma que representan.
C#
using System;
Side = length;
Length = length;
Width = width;
Radius = radius;
C#
using System;
new Circle(3) };
$"perimeter, {Shape.GetPerimeter(shape)}");
continue;
continue;
// Diagonal: 7.07
Dado que los objetos son polimórficos, es posible que una variable de un tipo de clase
base contenga un tipo derivado. Para acceder a los miembros de instancia del tipo
derivado, es necesario volver a convertir el valor en el tipo derivado. Pero una
conversión conlleva el riesgo de producir una InvalidCastException. C# proporciona
instrucciones de coincidencia de patrones que realizan una conversión
condicionalmente, solo si se va a realizar correctamente. C# además proporciona los
operadores is y as para probar si un valor es de un tipo determinado.
C#
FeedMammals(g);
FeedMammals(a);
// Output:
// Eating.
TestForMammals(g);
TestForMammals(sn);
if (a is Mammal m)
m.Eat();
else
// You also can use the as operator and test for null
var m = o as Mammal;
if (m != null)
Console.WriteLine(m.ToString());
else
// Output:
// I am an animal.
class Animal
class SuperNova { }
También puede usar la misma sintaxis para probar si un tipo de valor que admite valores
NULL tiene un valor, como se muestra en el ejemplo siguiente:
C#
int i = 5;
PatternMatchingNullable(i);
int? j = null;
PatternMatchingNullable(j);
double d = 9.78654;
PatternMatchingNullable(d);
PatternMatchingSwitch(i);
PatternMatchingSwitch(j);
PatternMatchingSwitch(d);
Console.WriteLine(j);
else
switch (val)
Console.WriteLine(number);
break;
Console.WriteLine(number);
break;
Console.WriteLine(number);
break;
Console.WriteLine(number);
break;
Console.WriteLine(number);
break;
case null:
break;
default:
break;
is del ejemplo anterior no se limitan a los tipos de valor que admiten un valor NULL.
También puede usar esos patrones para probar si una variable de un tipo de referencia
tiene un valor o es null .
C#
Giraffe g = new();
UseIsOperator(g);
UseAsOperator(g);
UsePatternMatchingIs(g);
// an incompatible type.
SuperNova sn = new();
UseAsOperator(sn);
int i = 5;
UseAsWithNullable(i);
double d = 9.78654;
UseAsWithNullable(d);
if (a is Mammal)
Mammal m = (Mammal)a;
m.Eat();
if (a is Mammal m)
m.Eat();
Mammal? m = o as Mammal;
if (m is not null)
Console.WriteLine(m.ToString());
else
if (j is not null)
Console.WriteLine(j);
else
class Animal
class SuperNova { }
Como puede ver al comparar este código con el código de coincidencia de patrones, la
sintaxis de coincidencia de patrones proporciona características más sólidas mediante la
combinación de la prueba y la asignación en una sola instrucción. Use la sintaxis de
coincidencia de patrones siempre que sea posible.
Tutorial: Uso de la coincidencia de
patrones para compilar algoritmos
basados en tipos y basados en datos
Artículo • 20/02/2023 • Tiempo de lectura: 17 minutos
Puede escribir una funcionalidad que se comporta como si se hubiesen ampliado tipos
que pueden estar en otras bibliotecas. Los patrones también se usan para crear una
funcionalidad que la aplicación requiere y que no es una característica fundamental del
tipo que se está ampliando.
Requisitos previos
Se recomienda tener Visual Studio para Windows o Mac. Puede descargar una
versión gratuita desde la página de descargas de Visual Studio . Visual Studio
incluye el SDK de .NET.
También se puede usar el editor de Visual Studio Code . Deberá instalar el SDK
de .NET más reciente por separado.
Si prefiere usar otro editor, deberá instalar el SDK de .NET más reciente.
En este tutorial se da por supuesto que conoce bien C# y. NET, incluidos Visual Studio o
la CLI de .NET.
En este tutorial, creará y explorará una aplicación que toma datos entrantes de varios
orígenes externos para un solo escenario. Verá cómo la coincidencia de patrones
proporciona una forma eficaz de consumir y procesar esos datos de maneras que no
formaban parte del sistema original.
Considere un área metropolitana importante que usa peajes y precios estipulados para
las horas de mayor actividad con el fin de administrar el tráfico. Puede escribir una
aplicación que calcule los peajes de un vehículo en función de su tipo. Mejoras
posteriores incorporan precios basados en la cantidad de ocupantes del vehículo. Otras
mejoras agregan precios según la hora y el día de la semana.
Desde esa descripción breve, puede haber esbozado rápidamente una jerarquía de
objetos para modelar este sistema. Sin embargo, los datos provienen de varios orígenes,
como otros sistemas de administración de registros de vehículos. Estos sistemas ofrecen
distintas clases para modelar esos datos y no se tiene un modelo de objetos único que
puede usar. En este tutorial, usará estas clases simplificadas para modelar los datos del
vehículo desde dichos sistemas externos, tal como se muestra en el código siguiente:
C#
namespace ConsumerVehicleRegistration
namespace CommercialRegistration
namespace LiveryRegistration
Los objetos con los que necesita trabajar no están en una jerarquía de objetos que
coincida con sus objetivos. Es posible que trabaje con clases que forman parte de
sistemas no relacionados.
La funcionalidad que agrega no forma parte de la abstracción central de estas
clases. El peaje que paga un vehículo cambia según los distintos tipos de vehículos,
pero el peaje no es una función central del vehículo.
Cuando la forma de los datos y las operaciones que se realizan en esos datos no se
describen en conjunto, las características de coincidencia de patrones de C# permiten
que sea más fácil trabajar con ellos.
Un Car es USD 2,00.
Un Taxi es USD 3,50.
Un Bus es USD 5,00.
Un DeliveryTruck es USD 10,00
using System;
using CommercialRegistration;
using ConsumerVehicleRegistration;
using LiveryRegistration;
namespace Calculators;
vehicle switch
};
El código anterior usa una expresión switch (que no es lo mismo que una instrucción
switch) que prueba el patrón de declaración. Una expresión switch comienza por la
variable, vehicle en el código anterior, seguida de la palabra clave switch . A
continuación, todos los segmentos modificadores aparecen entre llaves. La expresión
switch lleva a cabo otras mejoras en la sintaxis que rodea la instrucción switch . La
palabra clave case se omite y el resultado de cada segmento es una expresión. Los dos
últimos segmentos muestran una característica de lenguaje nueva. El caso { } coincide
con cualquier objeto no nulo que no coincidía con ningún segmento anterior. Este
segmento detecta todo tipo incorrecto que se pasa a este método. El caso { } debe
seguir los casos de cada tipo de vehículo. Si se invierte el orden, el caso { } tendrá
prioridad. Por último, el null patrón de constante detecta si se pasa null a este
método. El patrón null puede ser el último porque los otros patrones solo coinciden
con un objeto no nulo del tipo correcto.
C#
using System;
using CommercialRegistration;
using ConsumerVehicleRegistration;
using LiveryRegistration;
using toll_calculator;
try
catch (ArgumentException e)
try
tollCalc.CalculateToll(null!);
catch (ArgumentNullException e)
Ese código se incluye en el proyecto de inicio, pero se marca como comentario. Quite
los comentarios y podrá probar lo que escribió.
Empezará a ver cómo los patrones pueden ayudarlo a crear algoritmos cuando el
código y los datos están separados. La expresión switch prueba el tipo y genera valores
distintos en función de los resultados. Eso es solo el principio.
C#
vehicle switch
// ...
};
Los primeros tres casos prueban el tipo como Car y luego comprueban el valor de la
propiedad Passengers . Si ambos coinciden, esa expresión se evalúa y devuelve.
También podría expandir los casos para los taxis de manera similar:
C#
vehicle switch
// ...
// ...
};
C#
vehicle switch
// ...
// ...
};
C#
vehicle switch
// ...
};
C#
vehicle switch
};
Puede usar modificadores anidados para que este código sea menos repetitivo. Tanto
Car como Taxi tienen cuatro segmentos distintos en los ejemplos anteriores. En ambos
C#
vehicle switch
1 => 2.0m,
},
1 => 3.50m,
2 => 3.50m - 0.50m,
},
};
En el ejemplo anterior, el uso de una expresión recursiva significa que no repite los
segmentos Car y Taxi que contienen segmentos secundarios que prueban el valor de
propiedad. Esta técnica no se usa para los segmentos Bus y DeliveryTruck , porque esos
segmentos prueban intervalos para la propiedad, no valores discretos.
C#
if ((timeOfToll.DayOfWeek == DayOfWeek.Saturday) ||
(timeOfToll.DayOfWeek == DayOfWeek.Sunday))
return 1.0m;
else
if (hour < 6)
return 0.75m;
if (inbound)
return 2.0m;
else
return 1.0m;
return 1.5m;
if (inbound)
return 1.0m;
else
return 2.0m;
else // Overnight
return 0.75m;
El código anterior funciona correctamente, pero no es legible. Para que el código tenga
sentido, tiene que encadenar todos los casos de entrada y las instrucciones if
anidadas. En su lugar, usará la coincidencia de patrones para esta característica, pero la
integrará con otras técnicas. Podría crear una expresión de coincidencia de patrones
única que consideraría todas las combinaciones de dirección, día de la semana y hora. El
resultado sería una expresión complicada. Podría ser difícil de leer y de comprender.
Esto implica que es difícil garantizar su exactitud. En su lugar, combine esos método
para crear una tupla de valores que describa de manera concisa todos esos estados.
Luego, use la coincidencia de patrones para calcular un multiplicador para el peaje. La
tupla contiene tres condiciones discretas:
El sistema que cobra los peajes usa una estructura DateTime para la hora en que se
cobró el peaje. Genere métodos de miembro que creen las variables a partir de la tabla
anterior. La función siguiente usa una expresión switch de coincidencia de patrones para
expresar si la estructura DateTime representa un día laborable o un fin de semana:
C#
timeOfToll.DayOfWeek switch
};
Ese método es correcto, pero es redundante. Puede simplificarlo tal como se muestra en
el código siguiente:
C#
timeOfToll.DayOfWeek switch
_ => true
};
A continuación, agregue una función similar para categorizar la hora en los bloques:
C#
MorningRush,
Daytime,
EveningRush,
Overnight
timeOfToll.Hour switch
};
Agregue un elemento enum privado para convertir cada intervalo de tiempo en un valor
discreto. Después, el método GetTimeBand usa patrones relacionales y patrones or
conjuntivos, ambos agregados en C# 9.0. El patrón relacional permite probar un valor
numérico mediante < , > , <= o >= . El patrón or comprueba si una expresión coincide
con uno o más patrones. También puede usar un patrón and para asegurarse de que
una expresión coincide con dos patrones distintos, y un patrón not para probar que una
expresión no coincide con un patrón.
Después de usar esos métodos, puede usar otra expresión switch con el patrón de
tuplas para calcular el recargo en los precios. Puede crear una expresión switch con los
16 segmentos:
C#
};
El código anterior funciona, pero se puede simplificar. Las ocho combinaciones para el
fin de semana tienen el mismo peaje. Puede reemplazar las ocho por la siguiente línea:
C#
Tanto el tráfico hacia la ciudad como el tráfico desde la ciudad tienen el mismo
multiplicador durante el día y la noche de los fines de semana. Esos cuatro segmentos
modificadores se pueden reemplazar por las dos líneas siguientes:
C#
El código debe ser similar al código siguiente después de esos dos cambios:
C#
public decimal PeakTimePremium(DateTime timeOfToll, bool inbound) =>
};
Por último, puede quitar las dos horas punta en que se paga el precio regular. Cuando
quite esos segmentos, puede reemplazar false por un descarte ( _ ) en el segmento
modificador final. Tendrá el siguiente método finalizado:
C#
_ => 1.0m,
};
En este ejemplo se resalta una de las ventajas de la coincidencia de patrones: las ramas
del patrón se evalúan en orden. Si vuelve a ordenarlas para que una rama anterior
controle uno de los últimos casos, el compilador genera una advertencia sobre el
código inaccesible. Esas reglas de lenguaje facilitan la realización de las simplificaciones
anteriores con la confianza de que el código no cambió.
La coincidencia de patrones hace que algunos tipos de código sean más legibles y
ofrece una alternativa a las técnicas orientadas a objetos cuando no se puede agregar
código a las clases. La nube hace que los datos y la funcionalidad residan por separado.
La forma de los datos y las operaciones que se realizan en ellos no necesariamente se
describen en conjunto. En este tutorial, consumió datos existentes de maneras
totalmente distintas de su función original. La coincidencia de patrones le ha brindado
la capacidad de escribir una funcionalidad que reemplazase a esos tipos, aunque no le
permitía extenderlos.
Pasos siguientes
Puede descargar el código finalizado del repositorio GitHub dotnet/samples . Explore
los patrones por su cuenta y agregue esta técnica a sus actividades habituales de
codificación. Aprender estas técnicas le permite contar con otra forma de enfocar los
problemas y crear una funcionalidad nueva.
Vea también
Patrones
Expresión switch
Procedimientos para controlar una
excepción mediante Try y Catch
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
la mayoría de las veces lo único que se puede hacer es asegurarse de que se produzca
la excepción adecuada.
Ejemplo
En este ejemplo, IndexOutOfRangeException no es la excepción más adecuada. Tiene
más sentido la excepción ArgumentOutOfRangeException para el método, ya que el
error lo provoca el argumento index que pasa el autor de la llamada.
C#
try
return array[index];
Console.WriteLine(e.Message);
Comentarios
El código que produce una excepción está incluido en el bloque try . Se agrega una
instrucción catch inmediatamente después para controlar IndexOutOfRangeException , si
se produce. El bloque catch controla la excepción IndexOutOfRangeException y produce
en su lugar la excepción ArgumentOutOfRangeException , más adecuada. Para
proporcionar al autor de la llamada tanta información como sea posible, considere la
posibilidad de especificar la excepción original como InnerException de la nueva
excepción. Dado que la propiedad InnerException es read-only, debe asignarla en el
constructor de la nueva excepción.
Procedimiento para ejecutar código de
limpieza mediante finally
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
C#
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
file.Close();
Ejemplo
Para activar el código anterior en una instrucción try-catch-finally , el código de
limpieza se separa del código de trabajo, de esta forma.
C#
try
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
catch (UnauthorizedAccessException e)
Console.WriteLine(e.Message);
finally
file?.Close();
Dado que una excepción puede producirse en cualquier momento dentro del bloque
try antes de la llamada a OpenWrite() , o que podría producirse un error en la propia
llamada a OpenWrite() , no se garantiza que el archivo esté abierto cuando se intente
cerrar. El bloque finally agrega una comprobación para asegurarse de que el objeto
FileStream no sea null antes de que se pueda llamar al método Close. Sin la
comprobación null , el bloque finally podría iniciar su propia excepción
NullReferenceException, pero debería evitarse generar excepciones en bloques finally ,
si es posible.
Una conexión de base de datos es otra buena candidata para cerrarse en un bloque
finally . Dado que a veces se limita el número de conexiones permitido en un servidor
de base de datos, se deben cerrar las conexiones de base de datos tan pronto como sea
posible. Si se produce una excepción antes de poder cerrar la conexión, es mejor usar el
bloque finally que esperar a la recolección de elementos no utilizados.
Consulte también
using (instrucción)
try-catch
try-finally
try-catch-finally
Novedades de C# 11
Artículo • 21/02/2023 • Tiempo de lectura: 9 minutos
7 Nota
Atributos genéricos
Puede declarar una clase genérica cuya clase base sea System.Attribute. Esta
característica proporciona una sintaxis más cómoda para los atributos que requieren un
parámetro System.Type. Antes había que crear un atributo que tomara Type como
parámetro de constructor:
C#
// Before C# 11:
C#
[TypeAttribute(typeof(string))]
C#
// C# 11 feature:
C#
[GenericAttribute<string>()]
Debe proporcionar todos los parámetros de tipo al aplicar el atributo. En otras palabras,
el tipo genérico debe construirse completamente.
En el ejemplo anterior, los paréntesis
vacíos ( ( y ) ) se pueden omitir, ya que el atributo no tiene ningún argumento.
C#
Los argumentos de tipo deben cumplir las mismas restricciones que el operador typeof.
No se permiten los tipos que requieren anotaciones de metadatos. Por ejemplo, no se
permiten los siguientes tipos como parámetro de tipo:
dynamic
Puede agregar static abstract o static virtual miembros en interfaces para definir
interfaces que incluyan operadores sobrecargables, otros miembros estáticos y
propiedades estáticas. El escenario principal de esta característica es usar operadores
matemáticos en tipos genéricos. Por ejemplo, puede implementar la interfaz de
System.IAdditionOperators<TSelf, TOther, TResult> en un tipo que implementa
Patrones de lista
Los patrones de lista amplían la coincidencia de patrones para buscar coincidencias con
secuencias de elementos de una lista o una matriz. Por ejemplo, sequence is [1, 2, 3]
es true cuando sequence es una matriz o una lista de tres enteros (1, 2 y 3). Puede
hacer coincidir elementos mediante cualquier patrón, como constante, tipo, propiedad y
patrones relacionales. El patrón de descarte ( _ ) coincide con cualquier elemento único y
el nuevo patrón de intervalo ( .. ) coincide con cualquier secuencia de cero o más
elementos.
Puede encontrar más información sobre los patrones de lista en el artículo sobre
coincidencia de patrones en la referencia del lenguaje.
C#
""";
Cualquier espacio en blanco a la izquierda de las comillas dobles de cierre se quitará del
literal de cadena. Los literales de cadena sin formato se pueden combinar con la
interpolación de cadenas para incluir llaves en el texto de salida. Varios caracteres $
indican cuántas llaves consecutivas comienzan y terminan la interpolación:
C#
""";
En el ejemplo anterior se especifica que dos llaves inician y finalizan una interpolación.
La tercera llave de apertura y cierre repetida se incluye en la cadena de salida.
Puede encontrar más información sobre los literales de cadena sin formato en el artículo
sobre cadenas de la guía de programación y los artículos de referencia del lenguaje
sobre literales de cadena y cadenas interpoladas.
Puede obtener más información sobre los literales de cadena UTF-8 en la sección literal
de cadena del artículo sobre los tipos de referencia integrados.
Miembros requeridos
Puede agregar el modificadorrequired a propiedades y campos para aplicar
constructores y llamadores para inicializar esos valores. El
System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute se puede agregar a
constructores para informar al compilador de que un constructor inicializa todos los
miembros necesarios.
Para obtener más información sobre los miembros necesarios, consulte la sección solo
inicial del artículo de propiedades.
Vea también
Novedades de .NET 7
Novedades de C# 10
Artículo • 21/02/2023 • Tiempo de lectura: 6 minutos
Structs de registro
Mejoras de tipos de estructura
Controladores de cadena interpolada
Directivas global using
Declaración de espacios de nombres con ámbito de archivo
Patrones de propiedades extendidos
Mejoras en expresiones lambda
Se permiten cadenas interpoladas const
Los tipos de registro pueden sellar ToString()
Asignación definitiva mejorada
Se permite la asignación y la declaración en la misma desconstrucción
Se permite el atributo AsyncMethodBuilder en los métodos
Atributo CallerArgumentExpression
Pragma #line mejorado
Ola de advertencias 6
C# 10 es compatible con .NET 6. Para obtener más información, vea Control de
versiones del lenguaje C#.
7 Nota
Structs de registro
Puede declarar los registros de tipo de valor mediante las declaraciones record struct o
readonly record struct. Ahora puede aclarar que un elemento record es un tipo de
referencia con la declaración record class .
Mejoras de tipos de estructura
C# 10 presenta las mejoras siguientes relacionadas con los tipos de estructura:
C#
namespace MyNamespace;
Esta nueva sintaxis ahorra espacio horizontal y vertical para las declaraciones namespace .
C#
{ Prop1.Prop2: pattern }
C#
Las expresiones lambda pueden tener un tipo natural, donde el compilador puede
deducir un tipo delegado de la expresión lambda o del grupo de métodos.
Las expresiones lambda pueden declarar un tipo de valor devuelto cuando el
compilador no puede deducirlo.
Los atributos se pueden aplicar a las expresiones lambda.
Estas características hacen que las expresiones lambda sean parecidas a los métodos y
las funciones locales. Facilitan el uso de expresiones lambda sin declarar una variable de
un tipo delegado y funcionan de forma más fluida con las nuevas API mínimas de
ASP.NET CORE.
C#
// Initialization:
// assignment:
int x1 = 0;
int y1 = 0;
C#
int x = 0;
C#
// Or, using ?.
// Or, using ??
El impacto principal de esta mejora es que las advertencias para la asignación definitiva
y el análisis de estado NULL son más precisas.
Para obtener más información, vea la sección sobre AsyncMethodBuilder del artículo
sobre los atributos leídos por el compilador.
C#
if (!condition)
Puede obtener más información sobre esta característica en el artículo sobre Atributos
de información del autor de la llamada en la sección de referencia del lenguaje.
Registros
Establecedores de solo inicialización
Instrucciones de nivel superior
Mejoras de coincidencia de patrones
Rendimiento e interoperabilidad
Enteros con tamaño nativos
Punteros de función
Supresión de la emisión de la marca localsinit
Características de ajuste y finalización
Expresiones new con tipo de destino
Funciones anónimas static
Expresiones condicionales con tipo de destino
Tipos de valor devueltos de covariante
Compatibilidad con extensiones GetEnumerator para bucles foreach
Parámetros de descarte lambda
Atributos en funciones locales
Compatibilidad con generadores de código
Inicializadores de módulo
Nuevas características para métodos parciales
Ola de advertencias 5
C# 9.0 es compatible con .NET 5. Para obtener más información, vea Control de
versiones del lenguaje C#.
Tipos de registro
C# 9.0 introduce los tipos de registro. Se usa la palabra clave record para definir un tipo
de referencia que proporciona funcionalidad integrada para encapsular los datos. Puede
crear tipos de registros con propiedades inmutables mediante parámetros posicionales
o sintaxis de propiedades estándar:
C#
C#
};
C#
};
Aunque los registros pueden ser mutables, están destinados principalmente a admitir
modelos de datos inmutables. El tipo de registro ofrece las siguientes características:
Puede utilizar tipos de estructura para diseñar tipos centrados en datos que
proporcionen igualdad de valores y un comportamiento escaso o inexistente. Pero, en el
caso de los modelos de datos relativamente grandes, los tipos de estructura tienen
algunas desventajas:
No admiten la herencia.
Son menos eficaces a la hora de determinar la igualdad de valores. En el caso de
los tipos de valor, el método ValueType.Equals usa la reflexión para buscar todos
los campos. En el caso de los registros, el compilador genera el método Equals . En
la práctica, la implementación de la igualdad de valores en los registros es
bastante más rápida.
Usan más memoria en algunos escenarios, ya que cada instancia tiene una copia
completa de todos los datos. Los tipos de registro son tipos de referencia, por lo
que una instancia de registro solo contiene una referencia a los datos.
Sintaxis posicional para la definición de propiedad
Puede usar parámetros posicionales para declarar propiedades de un registro e
inicializar los valores de propiedad al crear una instancia:
C#
Console.WriteLine(person);
Para obtener más información, vea Sintaxis posicional en el artículo de referencia del
lenguaje C# acerca de los registros.
Inmutabilidad
Un tipo de registro no es necesariamente inmutable. Puede declarar propiedades con
descriptores de acceso set y campos que no sean readonly . Sin embargo, aunque los
registros pueden ser mutables, facilitan la creación de modelos de datos inmutables. Las
propiedades que se crean mediante la sintaxis posicional son inmutables.
La inmutabilidad puede resultar útil si quiere que un tipo centrado en datos sea seguro
para subprocesos o un código hash quede igual en una tabla hash. Puede impedir que
se produzcan errores cuando se pasa un argumento por referencia a un método y el
método cambia inesperadamente el valor del argumento.
Las características exclusivas de los tipos de registro se implementan mediante métodos
sintetizados por el compilador, y ninguno de estos métodos pone en peligro la
inmutabilidad mediante la modificación del estado del objeto.
Igualdad de valores
La igualdad de valores significa que dos variables de un tipo de registro son iguales si
los tipos coinciden y todos los valores de propiedad y campo coinciden. Para otros tipos
de referencia, la igualdad significa identidad. Es decir, dos variables de un tipo de
referencia son iguales si hacen referencia al mismo objeto.
C#
person1.PhoneNumbers[0] = "555-1234";
En los tipos class , podría invalidar manualmente los métodos y los operadores de
igualdad para lograr la igualdad de valores, pero el desarrollo y las pruebas de ese
código serían lentos y propensos a errores. Al tener esta funcionalidad integrada, se
evitan los errores que resultarían de olvidarse de actualizar el código de invalidación
personalizado cuando se agreguen o cambien propiedades o campos.
Para obtener más información, vea Igualdad de valores en el artículo de referencia del
lenguaje C# acerca de los registros.
Mutación no destructiva
Si necesita mutar propiedades inmutables de una instancia de registro, puede usar una
expresión with para lograr una mutación no destructiva. Una expresión with crea una
instancia de registro que es una copia de una instancia de registro existente, con las
propiedades y los campos especificados modificados. Use la sintaxis del inicializador de
objeto para especificar los valores que se van a cambiar, como se muestra en el ejemplo
siguiente:
C#
Console.WriteLine(person1);
Console.WriteLine(person2);
Console.WriteLine(person2);
Para obtener más información, vea Formato integrado en el artículo de referencia del
lenguaje C# acerca de los registros.
Herencia
Un registro puede heredar de otro registro. Sin embargo, un registro no puede heredar
de una clase, y una clase no puede heredar de un registro.
C#
: Person(FirstName, LastName);
Console.WriteLine(teacher);
Para que dos variables de registro sean iguales, el tipo en tiempo de ejecución debe ser
el mismo. Los tipos de las variables contenedoras podrían ser diferentes. Esto se
muestra en el siguiente código de ejemplo:
C#
: Person(FirstName, LastName);
: Person(FirstName, LastName);
En el ejemplo, todas las instancias tienen las mismas propiedades y los mismos valores
de propiedad. Pero student == teacher devuelve False aunque ambas sean variables
de tipo Person . Y student == student2 devuelve True aunque una sea una variable
Person y otra sea una variable Student .
Todas las propiedades y los campos públicos de los tipos derivados y base se incluyen
en la salida ToString , como se muestra en el ejemplo siguiente:
C#
: Person(FirstName, LastName);
: Person(FirstName, LastName);
Console.WriteLine(teacher);
Para obtener más información, vea Herencia en el artículo de referencia del lenguaje C#
acerca de los registros.
Puede declarar los establecedores de solo init en cualquier tipo que escriba. Por
ejemplo, en la estructura siguiente se define una estructura de observación
meteorológica:
C#
C#
RecordedAt = DateTime.Now,
TemperatureInCelsius = 20,
PressureInMillibars = 998.0m
};
C#
// Error! CS8852.
now.TemperatureInCelsius = 18;
Los establecedores de solo inicialización pueden ser útiles para establecer las
propiedades de clase base de las clases derivadas. También pueden establecer
propiedades derivadas mediante asistentes en una clase base. Los registros posicionales
declaran propiedades mediante establecedores de solo inicialización. Esos
establecedores se usan en expresiones with. Puede declarar establecedores de solo
inicialización para cualquier objeto class , struct o record que defina.
C#
using System;
namespace HelloWorld
class Program
Console.WriteLine("Hello World!");
Solo hay una línea de código que haga algo. Con las instrucciones de nivel superior,
puede reemplazar todo lo que sea reutilizable por la directiva using y la línea única que
realiza el trabajo:
C#
using System;
Console.WriteLine("Hello World!");
Si quisiera un programa de una línea, podría quitar la directiva using y usar el nombre
de tipo completo:
C#
System.Console.WriteLine("Hello World!");
Los patrones de tipo coinciden con un objeto que coincide con un tipo
determinado.
Los patrones con paréntesis aplican o resaltan la prioridad de las combinaciones
de patrones
En los patrones and conjuntivos es necesario que los dos patrones coincidan.
En los patrones or disyuntivos es necesario que alguno de los patrones coincida
En los patrones not negados es necesario que un patrón no coincida.
Los patrones relacionales requieren que la entrada sea menor que, mayor que,
menor o igual que, o mayor o igual que una constante determinada.
Estos patrones enriquecen la sintaxis de los patrones. Tenga en cuenta estos ejemplos:
C#
c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';
Con paréntesis opcionales para que quede claro que and tiene mayor prioridad que or :
C#
c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ',';
Uno de los usos más comunes es una nueva sintaxis para una comprobación NULL:
C#
if (e is not null)
// ...
Para obtener más información, vea las secciones Patrones relacionales y Patrones lógicos
del artículo Patrones.
Rendimiento e interoperabilidad
Tres nuevas características mejoran la compatibilidad con la interoperabilidad nativa y
las bibliotecas de bajo nivel que requieren alto rendimiento: enteros de tamaño nativo,
punteros de función y la omisión de la marca localsinit .
Los enteros de tamaño nativo, nint y nuint , son tipos enteros. Se expresan mediante
los tipos subyacentes System.IntPtr y System.UIntPtr. El compilador muestra las
conversiones y operaciones adicionales para estos tipos como enteros nativos. Los
enteros con tamaño nativo definen propiedades para MaxValue o MinValue . Estos
valores no se pueden expresar como constantes en tiempo de compilación porque
dependen del tamaño nativo de un entero en el equipo de destino. Estos valores son de
solo lectura en tiempo de ejecución. Puede usar valores constantes para nint en el
intervalo [ int.MinValue .. int.MaxValue ]. Puede usar valores constantes para nuint en el
intervalo [ uint.MinValue .. uint.MaxValue ]. El compilador realiza un plegamiento
constante para todos los operadores unarios y binarios que usan los tipos System.Int32
y System.UInt32. Si el resultado no cabe en 32 bits, la operación se ejecuta en tiempo de
ejecución y no se considera una constante. Los enteros con tamaño nativo pueden
aumentar el rendimiento en escenarios en los que se usa la aritmética de enteros y es
necesario tener el rendimiento más rápido posible. Para obtener más información,
consulte los tipos nint y nuint.
Los punteros de función proporcionan una sintaxis sencilla para acceder a los códigos
de operación de lenguaje intermedio ldftn y calli . Puede declarar punteros de
función con la nueva sintaxis de delegate* . Un tipo delegate* es un tipo de puntero. Al
invocar el tipo delegate* se usa calli , a diferencia de un delegado que usa callvirt
en el método Invoke() . Sintácticamente, las invocaciones son idénticas. La invocación
del puntero de función usa la convención de llamada managed . Agregue la palabra clave
unmanaged después de la sintaxis de delegate* para declarar que quiere la convención
C#
El tipo de destino new también se puede usar cuando es necesario crear un objeto para
pasarlo como argumento a un método. Considere un método ForecastFor() con la
signatura siguiente:
C#
C#
Otra aplicación muy útil de esta característica es para combinarla con propiedades de
solo inicialización para inicializar un objeto nuevo:
C#
Puede devolver una instancia creada por el constructor predeterminado mediante una
declaración return new(); .
Los tipos de valor devuelto covariantes proporcionan flexibilidad a los tipos de valor
devuelto de los métodos override. Un método override puede devolver un tipo derivado
del tipo de valor devuelto del método base invalidado. Esto puede ser útil para los
registros y para otros tipos que admiten métodos de generador o clonación virtuales.
Después, puede usar descartes como parámetros para las expresiones lambda. De esta
forma no tiene que asignar un nombre al argumento y el compilador puede evitar
usarlo. Use _ para cualquier argumento. Para más información, consulte sección sobre
parámetros de entrada de una expresión lambda en el artículo sobre expresiones
lambda.
Por último, ahora puede aplicar atributos a las funciones locales. Por ejemplo, puede
aplicar anotaciones de atributo que admiten un valor NULL a las funciones locales.
Un generador de código lee atributos u otros elementos de código mediante las API de
análisis de Roslyn. A partir de esa información, agrega código nuevo a la compilación.
Los generadores de código fuente solo pueden agregar código; no se les permite
modificar ningún código existente en la compilación.
Ese último punto significa en realidad que el método y su clase contenedora deben ser
internos o públicos. El método no puede ser una función local. Para obtener más
información, vea Atributo ModuleInitializer.
Historia de C#
Artículo • 09/03/2023 • Tiempo de lectura: 17 minutos
En este artículo se proporciona un historial de cada versión principal del lenguaje C#. El
equipo de C# continúa innovando y agregando nuevas características. Se puede
encontrar información sobre el estado detallado de las características de lenguaje,
incluidas las características consideradas para las próximas versiones, en el repositorio
dotnet/roslyn de GitHub.
) Importante
C# versión 11
Fecha de publicación noviembre de 2022
C# versión 10
Fecha de publicación noviembre de 2021
Structs de registro
Mejoras de tipos de estructura
Controladores de cadena interpolada
Directivas global using
Declaración de espacios de nombres con ámbito de archivo
Patrones de propiedades extendidos
Mejoras en expresiones lambda
Se permiten cadenas interpoladas const
Los tipos de registro pueden sellar ToString()
Asignación definitiva mejorada
Se permite la asignación y la declaración en la misma desconstrucción
Se permite el atributo AsyncMethodBuilder en los métodos
Atributo CallerArgumentExpression
Pragma #line mejorado
Hay más características disponibles en el modo de vista previa. Para poder usar estas
características, debe establecer <LangVersion> en Preview en el proyecto:
Muchas de las características le permiten escribir menos código para expresar los
mismos conceptos. Los structs de registro sintetizan muchos de los mismos métodos
que las clases de registro. Los structs y los tipos anónimos son compatibles con las
expresiones. Las directivas de uso global y las declaraciones de espacio de nombres con
ámbito de archivo consiguen que exprese las dependencias y la organización del espacio
de nombres con mayor claridad. Las mejoras de lambda facilitan la declaración de
expresiones lambda en las que se usan. Los nuevos patrones de propiedad y las mejoras
de deconstrucción crean un código más conciso.
C# versión 9
Fecha de publicación noviembre de 2020
Registros
Establecedores de solo inicialización
Instrucciones de nivel superior
Mejoras de coincidencia de patrones
Rendimiento e interoperabilidad
Enteros con tamaño nativos
Punteros de función
Supresión de la emisión de la marca localsinit
Características de ajuste y finalización
Expresiones new con tipo de destino
Funciones anónimas static
Expresiones condicionales con tipo de destino
Tipos de valor devueltos de covariante
Compatibilidad con extensiones GetEnumerator para bucles foreach
Parámetros de descarte lambda
Atributos en funciones locales
Compatibilidad con generadores de código
Inicializadores de módulo
Nuevas características para métodos parciales
C# 9 continúa tres de los temas de versiones anteriores: quitar complejidad, separar
datos de algoritmos y proporcionar más patrones en más lugares.
Las instrucciones de nivel superior hacen que el programa principal sea más fácil de leer.
La complejidad es innecesaria: un espacio de nombres, una clase Program y static void
Main() son innecesarios.
La introducción de records proporciona una sintaxis concisa para los tipos de referencia
que siguen la semántica del valor para la igualdad. Usará estos tipos para definir
contenedores de datos que normalmente definen un comportamiento mínimo. Los
establecedores de solo inicialización proporcionan la funcionalidad para la mutación no
destructiva with (expresiones) en los registros. C# 9 también agrega tipos de valor
devuelto de covariante para que los registros derivados puedan invalidar los métodos
virtuales y devolver un tipo derivado del tipo de valor devuelto del método base.
Los tipos nint y nuint modelan los tipos enteros de tamaño nativo en la CPU de
destino.
Los punteros de función proporcionan una funcionalidad similar a la de un
delegado, al mismo tiempo que evitan las asignaciones necesarias para crear un
objeto delegado.
La instrucción localsinit se puede omitir para guardar instrucciones.
Otro conjunto de mejoras admite escenarios en los que los generadores de código
agregan funcionalidad:
Los inicializadores de módulo son métodos a los que el runtime llama cuando se
carga un ensamblado.
Los métodos parciales admiten nuevos modificadores de acceso y tipos de valor
devuelto distintos de void. En esos casos, se debe proporcionar una
implementación.
C# 9 agrega muchas otras pequeñas características que mejoran la productividad del
desarrollador, tanto para escribir como para leer código:
La versión C# 9 continúa el trabajo para hacer que C# siga siendo un lenguaje de
programación moderno y de uso general. Las características siguen siendo compatibles
con cargas de trabajo y tipos de aplicación modernos.
C# versión 8.0
Fecha de publicación septiembre de 2019
C# 8.0 es la primera versión C# principal que tiene como destino específicamente .NET
Core. Algunas características se basan en nuevas funcionalidades de CLR, otras en tipos
de biblioteca agregados solo a .NET Core. C# 8.0 agrega las siguientes características y
mejoras al lenguaje C#:
C# versión 7.3
Fecha de publicación mayo de 2018
Hay dos temas principales para la versión C# 7.3. Un tema proporciona características
que permiten al código seguro ser tan eficaz como el código no seguro. El segundo
tema proporciona mejoras incrementales en las características existentes. También se
han agregado nuevas opciones de compilador en esta versión.
Las siguientes características nuevas admiten el tema del mejor rendimiento para código
seguro:
C# versión 7.1
Fecha de publicación agosto de 2017
C# empezó a publicar versiones de punto con C# 7.1. Esta versión agregó el elemento de
configuración de selección de versión de lenguaje, tres nuevas características de
lenguaje y un nuevo comportamiento del compilador.
Las nuevas características de lenguaje de esta versión son las siguientes:
asyncMain method
El punto de entrada de una aplicación puede tener el modificador async .
Expresiones literales default
Se pueden usar expresiones literales predeterminadas en expresiones de valor
predeterminadas cuando el tipo de destino se pueda inferir.
Nombres de elementos de tupla inferidos
En muchos casos, los nombres de elementos de tupla se pueden deducir de la
inicialización de la tupla.
Coincidencia de patrones en parámetros de tipo genérico
Puede usar expresiones de coincidencia de patrones en variables cuyo tipo es
un parámetro de tipo genérico.
Por último, el compilador tiene dos opciones, -refout y -refonly, que controlan la
generación de ensamblados de referencia.
C# versión 7.0
Fecha de publicación marzo de 2017
Variables out
Tuplas y deconstrucción
Coincidencia de patrones
Funciones locales
Miembros con forma de expresión expandidos
Variables locales de tipo ref
Devoluciones de referencias
Descartes
Literales binarios y separadores de dígitos
Expresiones throw
C# versión 6.0
Fecha de publicación julio de 2015
Versión 6.0, publicada con Visual Studio 2015, lanzó muchas características más
pequeñas que hicieron que la programación de C# sea más productiva. Estas son
algunas de ellas:
Importaciones estáticas
Filtros de excepciones
Inicializadores de propiedades automáticas
Miembros de cuerpo de expresión
Propagador de null
Interpolación de cadenas
operador nameof
Inicializadores de índice
Await en bloques catch y finally
Valores predeterminados para las propiedades solo de captador
En esta versión también se hizo otra cosa, aunque no es una característica de lenguaje
tradicional: publicaron el compilador Roslyn como un servicio . Ahora, el compilador
de C# está escrito en C# y puede usarlo como parte de su trabajo de programación.
C# versión 5.0
Fecha de publicación agosto de 2012
La versión 5.0 de C#, publicada con Visual Studio 2012, era una versión centrada del
lenguaje. Casi todo el trabajo de esa versión se centró en otro concepto de lenguaje
innovador: el modelo async y await para la programación asincrónica. Estas son las
principales características:
Miembros asincrónicos
Atributos de información del llamador
Proyecto de código: Atributos de información del autor de llamada en C# 5.0
Pero async y await son los auténticos protagonistas de esta versión. Cuando estas
características salieron a la luz en 2012, C# cambió de nuevo las reglas del juego al
integrar la asincronía en el lenguaje como un participante de primera clase. Si alguna
vez ha trabajado con operaciones de larga duración y la implementación de sitios web
de devoluciones de llamada, probablemente le haya encantado esta característica del
lenguaje.
C# versión 4.0
Fecha de publicación abril de 2010
La versión 4.0 de C#, publicada con Visual Studio 2010, tuvo que lidiar con el carácter
innovador que había adquirido la versión 3.0. Esta versión introdujo algunas
características nuevas interesantes:
Enlace dinámico
Argumentos opcionales/con nombre
Covariante y contravariante de genéricos
Tipos de interoperabilidad insertados
Los enlaces dinámicos pueden dar lugar a errores, pero también otorgan un gran poder
sobre el lenguaje.
C# versión 3.0
Fecha de publicación noviembre de 2007
La versión 3.0 de C# llegó a finales de 2007, junto con Visual Studio 2008, aunque la
cartera completa de características de lenguaje no llegaría realmente hasta la versión 3.5
de .NET Framework. Esta versión marcó un cambio importante en el crecimiento de C#.
Estableció C# como un lenguaje de programación realmente formidable. Echemos un
vistazo a algunas de las principales características de esta versión:
Una vista más matizada examina árboles de expresión, expresiones lambda y tipos
anónimos como la base sobre la que se construye LINQ. Sin embargo, en cualquier caso,
C# 3.0 presentó un concepto revolucionario. C# 3.0 había comenzado a sentar las bases
para convertir C# en un lenguaje híbrido funcional y orientado a objetos.
C# versión 2.0
Fecha de publicación noviembre de 2005
Genéricos
Tipos parciales
Métodos anónimos
Tipos de valores que aceptan valores NULL
Iteradores
Covarianza y contravarianza
C# 2.0 incorporó los iteradores. Para explicarlo brevemente, los iteradores permiten
examinar todos los elementos de List (u otros tipos enumerables) con un bucle de
foreach . Tener iteradores como una parte de primera clase del lenguaje mejoró
Aun así, C# seguía yendo por detrás de Java. Java ya había publicado versiones que
incluían genéricos e iteradores. Pero esto cambiaría pronto a medida que los idiomas
siguieran evolucionando.
Versión 1.2 de C#
Fecha de publicación abril de 2003
Versión 1.2 de C# incluida en Visual Studio .NET 2003. Contenía algunas pequeñas
mejoras del lenguaje. Lo más notable es que, a partir de esa versión, el código se
generaba en un bucle foreach llamado Dispose en un IEnumerator cuando ese
IEnumerator implementaba IDisposable.
C# versión 1.0
Fecha de publicación enero de 2002
Si echa la vista atrás, la versión 1.0 de C#, publicada con Visual Studio .NET 2002, se
parecía mucho a Java. Como parte de sus objetivos de diseño declarados para ECMA ,
buscaba ser un “lenguaje sencillo, moderno, orientado a objetos y de uso general”. En
aquella época, parecerse a Java suponía conseguir esos primeros objetivos de diseño.
Pero si volvemos a echarle un vistazo a C# 1.0 ahora, no lo verá tan claro. Carecía de
capacidades asincrónicas integradas y de algunas funcionalidades útiles de genéricos
que se dan por sentado. De hecho, carecía por completo de genéricos. ¿Y LINQ? Aún no
estaba disponible. Esas características tardarían unos años más en agregarse.
Clases
Structs
Interfaces
Eventos
Propiedades
Delegados
Operadores y expresiones
Instrucciones
Atributos
En este artículo se proporciona un historial de cada versión principal del lenguaje C#. El
equipo de C# continúa innovando y agregando nuevas características. Se puede
encontrar información sobre el estado detallado de las características de lenguaje,
incluidas las características consideradas para las próximas versiones, en el repositorio
dotnet/roslyn de GitHub.
) Importante
C# versión 11
Fecha de publicación noviembre de 2022
C# versión 10
Fecha de publicación noviembre de 2021
Structs de registro
Mejoras de tipos de estructura
Controladores de cadena interpolada
Directivas global using
Declaración de espacios de nombres con ámbito de archivo
Patrones de propiedades extendidos
Mejoras en expresiones lambda
Se permiten cadenas interpoladas const
Los tipos de registro pueden sellar ToString()
Asignación definitiva mejorada
Se permite la asignación y la declaración en la misma desconstrucción
Se permite el atributo AsyncMethodBuilder en los métodos
Atributo CallerArgumentExpression
Pragma #line mejorado
Hay más características disponibles en el modo de vista previa. Para poder usar estas
características, debe establecer <LangVersion> en Preview en el proyecto:
Muchas de las características le permiten escribir menos código para expresar los
mismos conceptos. Los structs de registro sintetizan muchos de los mismos métodos
que las clases de registro. Los structs y los tipos anónimos son compatibles con las
expresiones. Las directivas de uso global y las declaraciones de espacio de nombres con
ámbito de archivo consiguen que exprese las dependencias y la organización del espacio
de nombres con mayor claridad. Las mejoras de lambda facilitan la declaración de
expresiones lambda en las que se usan. Los nuevos patrones de propiedad y las mejoras
de deconstrucción crean un código más conciso.
C# versión 9
Fecha de publicación noviembre de 2020
Registros
Establecedores de solo inicialización
Instrucciones de nivel superior
Mejoras de coincidencia de patrones
Rendimiento e interoperabilidad
Enteros con tamaño nativos
Punteros de función
Supresión de la emisión de la marca localsinit
Características de ajuste y finalización
Expresiones new con tipo de destino
Funciones anónimas static
Expresiones condicionales con tipo de destino
Tipos de valor devueltos de covariante
Compatibilidad con extensiones GetEnumerator para bucles foreach
Parámetros de descarte lambda
Atributos en funciones locales
Compatibilidad con generadores de código
Inicializadores de módulo
Nuevas características para métodos parciales
C# 9 continúa tres de los temas de versiones anteriores: quitar complejidad, separar
datos de algoritmos y proporcionar más patrones en más lugares.
Las instrucciones de nivel superior hacen que el programa principal sea más fácil de leer.
La complejidad es innecesaria: un espacio de nombres, una clase Program y static void
Main() son innecesarios.
La introducción de records proporciona una sintaxis concisa para los tipos de referencia
que siguen la semántica del valor para la igualdad. Usará estos tipos para definir
contenedores de datos que normalmente definen un comportamiento mínimo. Los
establecedores de solo inicialización proporcionan la funcionalidad para la mutación no
destructiva with (expresiones) en los registros. C# 9 también agrega tipos de valor
devuelto de covariante para que los registros derivados puedan invalidar los métodos
virtuales y devolver un tipo derivado del tipo de valor devuelto del método base.
Los tipos nint y nuint modelan los tipos enteros de tamaño nativo en la CPU de
destino.
Los punteros de función proporcionan una funcionalidad similar a la de un
delegado, al mismo tiempo que evitan las asignaciones necesarias para crear un
objeto delegado.
La instrucción localsinit se puede omitir para guardar instrucciones.
Otro conjunto de mejoras admite escenarios en los que los generadores de código
agregan funcionalidad:
Los inicializadores de módulo son métodos a los que el runtime llama cuando se
carga un ensamblado.
Los métodos parciales admiten nuevos modificadores de acceso y tipos de valor
devuelto distintos de void. En esos casos, se debe proporcionar una
implementación.
C# 9 agrega muchas otras pequeñas características que mejoran la productividad del
desarrollador, tanto para escribir como para leer código:
La versión C# 9 continúa el trabajo para hacer que C# siga siendo un lenguaje de
programación moderno y de uso general. Las características siguen siendo compatibles
con cargas de trabajo y tipos de aplicación modernos.
C# versión 8.0
Fecha de publicación septiembre de 2019
C# 8.0 es la primera versión C# principal que tiene como destino específicamente .NET
Core. Algunas características se basan en nuevas funcionalidades de CLR, otras en tipos
de biblioteca agregados solo a .NET Core. C# 8.0 agrega las siguientes características y
mejoras al lenguaje C#:
C# versión 7.3
Fecha de publicación mayo de 2018
Hay dos temas principales para la versión C# 7.3. Un tema proporciona características
que permiten al código seguro ser tan eficaz como el código no seguro. El segundo
tema proporciona mejoras incrementales en las características existentes. También se
han agregado nuevas opciones de compilador en esta versión.
Las siguientes características nuevas admiten el tema del mejor rendimiento para código
seguro:
C# versión 7.1
Fecha de publicación agosto de 2017
C# empezó a publicar versiones de punto con C# 7.1. Esta versión agregó el elemento de
configuración de selección de versión de lenguaje, tres nuevas características de
lenguaje y un nuevo comportamiento del compilador.
Las nuevas características de lenguaje de esta versión son las siguientes:
asyncMain method
El punto de entrada de una aplicación puede tener el modificador async .
Expresiones literales default
Se pueden usar expresiones literales predeterminadas en expresiones de valor
predeterminadas cuando el tipo de destino se pueda inferir.
Nombres de elementos de tupla inferidos
En muchos casos, los nombres de elementos de tupla se pueden deducir de la
inicialización de la tupla.
Coincidencia de patrones en parámetros de tipo genérico
Puede usar expresiones de coincidencia de patrones en variables cuyo tipo es
un parámetro de tipo genérico.
Por último, el compilador tiene dos opciones, -refout y -refonly, que controlan la
generación de ensamblados de referencia.
C# versión 7.0
Fecha de publicación marzo de 2017
Variables out
Tuplas y deconstrucción
Coincidencia de patrones
Funciones locales
Miembros con forma de expresión expandidos
Variables locales de tipo ref
Devoluciones de referencias
Descartes
Literales binarios y separadores de dígitos
Expresiones throw
C# versión 6.0
Fecha de publicación julio de 2015
Versión 6.0, publicada con Visual Studio 2015, lanzó muchas características más
pequeñas que hicieron que la programación de C# sea más productiva. Estas son
algunas de ellas:
Importaciones estáticas
Filtros de excepciones
Inicializadores de propiedades automáticas
Miembros de cuerpo de expresión
Propagador de null
Interpolación de cadenas
operador nameof
Inicializadores de índice
Await en bloques catch y finally
Valores predeterminados para las propiedades solo de captador
En esta versión también se hizo otra cosa, aunque no es una característica de lenguaje
tradicional: publicaron el compilador Roslyn como un servicio . Ahora, el compilador
de C# está escrito en C# y puede usarlo como parte de su trabajo de programación.
C# versión 5.0
Fecha de publicación agosto de 2012
La versión 5.0 de C#, publicada con Visual Studio 2012, era una versión centrada del
lenguaje. Casi todo el trabajo de esa versión se centró en otro concepto de lenguaje
innovador: el modelo async y await para la programación asincrónica. Estas son las
principales características:
Miembros asincrónicos
Atributos de información del llamador
Proyecto de código: Atributos de información del autor de llamada en C# 5.0
Pero async y await son los auténticos protagonistas de esta versión. Cuando estas
características salieron a la luz en 2012, C# cambió de nuevo las reglas del juego al
integrar la asincronía en el lenguaje como un participante de primera clase. Si alguna
vez ha trabajado con operaciones de larga duración y la implementación de sitios web
de devoluciones de llamada, probablemente le haya encantado esta característica del
lenguaje.
C# versión 4.0
Fecha de publicación abril de 2010
La versión 4.0 de C#, publicada con Visual Studio 2010, tuvo que lidiar con el carácter
innovador que había adquirido la versión 3.0. Esta versión introdujo algunas
características nuevas interesantes:
Enlace dinámico
Argumentos opcionales/con nombre
Covariante y contravariante de genéricos
Tipos de interoperabilidad insertados
Los enlaces dinámicos pueden dar lugar a errores, pero también otorgan un gran poder
sobre el lenguaje.
C# versión 3.0
Fecha de publicación noviembre de 2007
La versión 3.0 de C# llegó a finales de 2007, junto con Visual Studio 2008, aunque la
cartera completa de características de lenguaje no llegaría realmente hasta la versión 3.5
de .NET Framework. Esta versión marcó un cambio importante en el crecimiento de C#.
Estableció C# como un lenguaje de programación realmente formidable. Echemos un
vistazo a algunas de las principales características de esta versión:
Una vista más matizada examina árboles de expresión, expresiones lambda y tipos
anónimos como la base sobre la que se construye LINQ. Sin embargo, en cualquier caso,
C# 3.0 presentó un concepto revolucionario. C# 3.0 había comenzado a sentar las bases
para convertir C# en un lenguaje híbrido funcional y orientado a objetos.
C# versión 2.0
Fecha de publicación noviembre de 2005
Genéricos
Tipos parciales
Métodos anónimos
Tipos de valores que aceptan valores NULL
Iteradores
Covarianza y contravarianza
C# 2.0 incorporó los iteradores. Para explicarlo brevemente, los iteradores permiten
examinar todos los elementos de List (u otros tipos enumerables) con un bucle de
foreach . Tener iteradores como una parte de primera clase del lenguaje mejoró
Aun así, C# seguía yendo por detrás de Java. Java ya había publicado versiones que
incluían genéricos e iteradores. Pero esto cambiaría pronto a medida que los idiomas
siguieran evolucionando.
Versión 1.2 de C#
Fecha de publicación abril de 2003
Versión 1.2 de C# incluida en Visual Studio .NET 2003. Contenía algunas pequeñas
mejoras del lenguaje. Lo más notable es que, a partir de esa versión, el código se
generaba en un bucle foreach llamado Dispose en un IEnumerator cuando ese
IEnumerator implementaba IDisposable.
C# versión 1.0
Fecha de publicación enero de 2002
Si echa la vista atrás, la versión 1.0 de C#, publicada con Visual Studio .NET 2002, se
parecía mucho a Java. Como parte de sus objetivos de diseño declarados para ECMA ,
buscaba ser un “lenguaje sencillo, moderno, orientado a objetos y de uso general”. En
aquella época, parecerse a Java suponía conseguir esos primeros objetivos de diseño.
Pero si volvemos a echarle un vistazo a C# 1.0 ahora, no lo verá tan claro. Carecía de
capacidades asincrónicas integradas y de algunas funcionalidades útiles de genéricos
que se dan por sentado. De hecho, carecía por completo de genéricos. ¿Y LINQ? Aún no
estaba disponible. Esas características tardarían unos años más en agregarse.
Clases
Structs
Interfaces
Eventos
Propiedades
Delegados
Operadores y expresiones
Instrucciones
Atributos
La definición del lenguaje C# exige que una biblioteca estándar tenga determinados
tipos y determinados miembros accesibles en esos tipos. El compilador genera código
que usa estos miembros y tipos necesarios para muchas características de lenguaje
diferentes. En caso necesario, hay paquetes NuGet que contienen tipos necesarios para
las versiones más recientes del lenguaje al escribir código para entornos donde esos
tipos o miembros aún no se han implementado.
Las versiones posteriores de C# a veces han agregado nuevos tipos o miembros a las
dependencias. Los ejemplos incluyen: INotifyCompletion, CallerFilePathAttribute y
CallerMemberNameAttribute. C# 7.0 continúa esta tendencia al agregar una
dependencia a ValueTuple para implementar la característica de lenguaje tuplas.
El equipo de diseño del lenguaje se esfuerza por minimizar el área expuesta de los tipos
y miembros necesarios en una biblioteca estándar compatible. Ese objetivo está
equilibrado con un diseño limpio donde las nuevas características de la biblioteca se
han incorporado sin problemas al lenguaje. Habrá nuevas características en versiones
futuras de C# que exijan nuevos tipos y miembros en una biblioteca estándar. Es
importante entender cómo administrar esas dependencias en el trabajo.
Administración de dependencias
Las herramientas del compilador de C# ahora se han desvinculado del ciclo de versiones
de las bibliotecas de .NET en las plataformas compatibles. De hecho, las distintas
bibliotecas de .NET tienen ciclos de versiones diferentes: .NET Framework en Windows
se distribuye como una actualización de Windows, .NET Core se distribuye conforme a
una programación independiente y las versiones de Xamarin de actualizaciones de la
biblioteca se incluyen en las herramientas de Xamarin de cada plataforma de destino.
En la mayoría de las ocasiones estos cambios no son perceptibles. Pero cuando trabaje
con una versión más reciente del lenguaje que requiera características aún no incluidas
en las bibliotecas de .NET de esa plataforma, haga referencia a los paquetes NuGet para
proporcionar esos nuevos tipos.
A medida que las plataformas que admite la aplicación
se actualicen con las nuevas instalaciones del marco de trabajo, puede quitar la
referencia adicional.
Esta desvinculación significa que puede usar nuevas características de lenguaje aun
cuando el destino sean equipos que no tengan el marco de trabajo correspondiente.
Consideraciones sobre versiones y
actualizaciones para desarrolladores de
C#
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Al adoptar nuevas características del lenguaje en una biblioteca, puede requerirse más
atención. Puede crear una biblioteca con características que se encuentran en la última
versión y necesita asegurarse de que las aplicaciones creadas con versiones anteriores
del compilador pueden usarla. O bien puede actualizar una biblioteca existente, con la
posibilidad de que muchos de los usuarios aún no tengan versiones actualizadas.
Cuando tome decisiones sobre la adopción de nuevas características, deberá tener en
cuenta dos variaciones de compatibilidad: la compatibilidad binaria y la compatibilidad
con el origen.
Cambios incompatibles
Si un cambio no es compatible con el origen ni compatible con elementos binarios, se
necesitan cambios en el código fuente junto con la recompilación en las bibliotecas y las
aplicaciones dependientes.
Evaluar la biblioteca
Estos conceptos de compatibilidad afectan a las declaraciones públicas y protegidas de
la biblioteca, no a su implementación interna. La adopción interna de nuevas
características siempre es compatible con elementos binarios.
Los cambios compatibles con elementos binarios proporcionan la nueva sintaxis que
genera el mismo código compilado para las declaraciones públicas que la sintaxis
anterior. Por ejemplo, cambiar un método a un miembro con cuerpo de expresión es un
cambio compatible con elementos binarios:
Código original:
C#
Nuevo código:
C#
Los cambios compatibles con el origen presentan sintaxis que cambia el código
compilado para un miembro público, pero de una forma compatible con los sitios de
llamada existentes. Por ejemplo, cambiar una firma de método de un parámetro por
valor a un parámetro por referencia in es compatible con el origen, pero no con
elementos binarios:
Código original:
C#
Nuevo código:
C#
Prerrequisitos
Deberá configurar la máquina para ejecutar .NET 7, que admite C# 11. El compilador de
C# 11 está disponible a partir de Visual Studio 2022, versión 17.3 o el SDK de .NET
7 .
C#
La misma lógica funcionaría para cualquier tipo numérico: int , short , long ,
float decimal o cualquier tipo que represente un número. Debe tener una manera de
usar los + operadores y / y para definir un valor para 2 . Puede usar la
System.Numerics.INumber<TSelf> interfaz para escribir el método anterior como
método genérico siguiente:
C#
Cualquier tipo que implemente la INumber<TSelf> interfaz debe incluir una definición
para operator + y para operator / . El denominador se define mediante
T.CreateChecked(2) para crear el valor 2 de cualquier tipo numérico, lo que obliga al
Los miembros abstractos estáticos se definen en una interfaz con una sintaxis conocida:
se agregan los static modificadores y abstract a cualquier miembro estático que no
proporcione una implementación. En el ejemplo siguiente se define una IGetNext<T>
interfaz que se puede aplicar a cualquier tipo que invalide operator ++ :
C#
Puede crear una estructura que cree una cadena de caracteres "A", donde cada
incremento agrega otro carácter a la cadena mediante el código siguiente:
C#
public struct RepeatSequence : IGetNext<RepeatSequence>
public RepeatSequence() {}
Por lo general, puede crear cualquier algoritmo en el que quiera definir ++ para que
"genere el siguiente valor de este tipo". El uso de esta interfaz genera código y
resultados claros:
C#
Console.WriteLine(str++);
PowerShell
AA
AAA
AAAA
AAAAA
AAAAAA
AAAAAAA
AAAAAAAA
AAAAAAAAA
AAAAAAAAAA
Empiece por crear una nueva aplicación de consola, ya sea mediante dotnet new o
Visual Studio. Establezca la versión del lenguaje C# en "versión preliminar", que habilita
las características en versión preliminar de C# 11. Agregue el siguiente elemento al
archivo csproj dentro de un <PropertyGroup> elemento :
XML
<LangVersion>preview</LangVersion>
7 Nota
C#
Use el record tipo para los Translation<T> tipos y Point<T> : ambos almacenan dos
valores y representan el almacenamiento de datos en lugar de un comportamiento
sofisticado. La implementación de operator + tendría el siguiente aspecto:
C#
public static Point<T> operator +(Point<T> left, Translation<T> right) =>
método estático. Declara tres parámetros de tipo: uno para el operando izquierdo, otro
para el operando derecho y otro para el resultado. Algunos tipos implementan + para
diferentes operandos y tipos de resultado. Agregue una declaración que el argumento
type implemente T IAdditionOperators<T, T, T> :
C#
Después de agregar esa restricción, la Point<T> clase puede usar para + su operador de
suma. Agregue la misma restricción en la Translation<T> declaración:
C#
C#
Console.WriteLine(pt);
Console.WriteLine(translate);
Console.WriteLine(final);
Puede hacer que este código sea más reutilizable declarando que estos tipos
implementan las interfaces aritméticas adecuadas. El primer cambio que se va a realizar
es declarar que Point<T, T> implementa la IAdditionOperators<Point<T>,
Translation<T>, Point<T>> interfaz . El Point tipo usa diferentes tipos para operandos y
el resultado. El Point tipo ya implementa un operator + con esa firma, por lo que
agregar la interfaz a la declaración es todo lo que necesita:
C#
Por último, al realizar la adición, resulta útil tener una propiedad que defina el valor de
identidad aditivo para ese tipo. Hay una nueva interfaz para esa característica:
IAdditiveIdentity<TSelf,TResult>. Una traducción de {0, 0} es la identidad de adición: el
punto resultante es el mismo que el operando izquierdo. La IAdditiveIdentity<TSelf,
TResult> interfaz define una propiedad readonly, AdditiveIdentity , que devuelve el
C#
using System.Numerics;
Hay algunos cambios aquí, así que vamos a recorrerlos uno por uno. En primer lugar,
declara que el Translation tipo implementa la IAdditiveIdentity interfaz :
C#
C#
public static Translation<T> AdditiveIdentity =>
C#
C#
using System.Numerics;
" Escriba un método que dependa de la INumber<T> interfaz para que se pueda usar
con cualquier tipo numérico.
" Cree un tipo que se base en las interfaces de adición para implementar un tipo que
solo admita una operación matemática.
Ese tipo declara su compatibilidad con esas
mismas interfaces para que se pueda componer de otras maneras. Los algoritmos
se escriben con la sintaxis más natural de los operadores matemáticos.
Experimente con estas características y registre los comentarios. Puede usar el elemento
de menú Enviar comentarios en Visual Studio o crear un nuevo problema en el
repositorio roslyn en GitHub. Compile algoritmos genéricos que funcionen con
cualquier tipo numérico. Compile algoritmos con estas interfaces en las que el
argumento type solo pueda implementar un subconjunto de funcionalidades de tipo
numérico. Aunque no cree nuevas interfaces que usen estas funcionalidades, puede
experimentar con ellas en los algoritmos.
Vea también
Matemáticas genéricas
Creación de tipos de registros
Artículo • 18/01/2023 • Tiempo de lectura: 13 minutos
C# 9 incorpora registros, un nuevo tipo de referencia que se puede crear en lugar de
clases o structs. C# 10 agrega estructuras de registro para que pueda definir registros
como tipos de valor. Los registros se diferencian de las clases en que los tipos de
registros usan igualdad basada en valores. Dos variables de un tipo de registro son
iguales si las definiciones del tipo de registro son idénticas y si, en cada campo, los
valores de ambos registros son iguales. Dos variables de un tipo de clase son iguales si
los objetos a los que se hace referencia son el mismo tipo de clase y las variables hacen
referencia al mismo objeto. La igualdad basada en valores conlleva otras capacidades
que probablemente quiera en los tipos de registros. El compilador genera muchos de
esos miembros al declarar un elemento record en lugar de class . El compilador genera
esos mismos métodos para los tipos record struct .
Prerrequisitos
Tendrá que configurar la máquina para ejecutar .NET 6 o versiones posteriores, incluido
el compilador de C# 10 o versiones posteriores. El compilador de C# 10 está disponible
a partir de Visual Studio 2022 o del SDK de .NET 6 .
También puede declarar registros posicionales mediante una sintaxis más concisa. El
compilador sintetiza más métodos automáticamente cuando se declaran registros
posicionales:
C#
public readonly record struct DailyTemperature(double HighTemp, double
LowTemp);
tiene un constructor primario con dos parámetros que coinciden con las dos
propiedades. Use el constructor primario para inicializar un registro DailyTemperature .
En el código siguiente se crea e inicializa varios registros DailyTemperature . El primero
usa parámetros con nombre para aclarar HighTemp y LowTemp . Los inicializadores
restantes usan parámetros posicionales para inicializar HighTemp y LowTemp :
C#
};
Puede agregar sus propias propiedades o métodos a los registros, incluidos los registros
posicionales. Tiene que calcular la temperatura media de cada día. Puede agregar esa
propiedad al registro DailyTemperature :
C#
public readonly record struct DailyTemperature(double HighTemp, double
LowTemp)
Vamos a asegurarnos de que puede usar estos datos. Agregue el código siguiente al
método Main :
C#
Console.WriteLine(item);
Ejecute la aplicación y verá un resultado similar a la siguiente pantalla (se han quitado
varias filas por motivos de espacio):
CLI de .NET
C#
: DegreeDays(BaseTemperature, TempRecords)
: DegreeDays(BaseTemperature, TempRecords)
C#
C#
stringBuilder.Append("HeatingDegreeDays");
stringBuilder.Append(" { ");
if (PrintMembers(stringBuilder))
stringBuilder.Append(" ");
stringBuilder.Append("}");
return stringBuilder.ToString();
C#
stringBuilder.Append($"BaseTemperature = {BaseTemperature}");
return true;
La firma declara un método virtual protected para que coincida con la versión del
compilador. No se preocupe si obtiene los descriptores de acceso incorrectos; el
lenguaje aplica la firma correcta. Si olvida los modificadores correctos de cualquier
método sintetizado, el compilador emite advertencias o errores que ayudan a obtener la
firma correcta.
Mutación no destructiva
Los miembros sintetizados de una clase de registro posicional no modifican el estado
del registro. El objetivo es que se puedan crear registros inmutables más fácilmente.
Recuerde que declara una instancia de readonly record struct para crear una
estructura de registro inmutable. Vuelva a observar las declaraciones anteriores de
HeatingDegreeDays y CoolingDegreeDays . Los miembros agregados realizan cálculos en
los valores del registro, pero no mutan el estado. Los registros posicionales facilitan la
creación de tipos de referencia inmutables.
C#
Console.WriteLine(growingDegreeDays);
Puede comparar el número de grados calculado con los números generados con una
temperatura de base de referencia superior. Recuerde que los registros son tipos de
referencia y estas copias son instantáneas. No se copia la matriz de los datos, sino que
ambos registros hacen referencia a los mismos datos. Ese hecho supone una ventaja en
otro escenario. En el caso de la suma térmica, es útil realizar el seguimiento del total de
los cinco días anteriores. Puede crear nuevos registros con otros datos de origen
mediante expresiones with . En el código siguiente se compila una colección de estas
acumulaciones y luego se muestran los valores:
C#
movingAccumulation.Add(fiveDayTotal);
Console.WriteLine();
Console.WriteLine(item);
También puede usar expresiones with para crear copias de registros. No especifique
ninguna propiedad entre las llaves de la expresión with . Eso significa crear una copia y
no cambiar ninguna propiedad:
C#
Resumen
En este tutorial se han mostrado varios aspectos de los registros. Los registros
proporcionan una sintaxis concisa para los tipos cuyo uso fundamental es el
almacenamiento de datos. En el caso de las clases orientadas a objetos, el uso
fundamental es definir responsabilidades. Este tutorial se ha centrado en los registros
posicionales, donde se puede usar una sintaxis concisa para declarar las propiedades de
un registro. El compilador sintetiza varios miembros del registro para copiar y comparar
registros. Puede agregar cualquier otro miembro que necesite para sus tipos de
registros. Puede crear tipos de registros inmutables sabiendo que ninguno de los
miembros generados por el compilador mutaría su estado. Además, las expresiones
with facilitan la compatibilidad con la mutación no destructiva.
Los registros presentan otra manera de definir tipos. Se usan definiciones class para
crear jerarquías orientadas a objetos que se centran en las responsabilidades y el
comportamiento de los objetos. Cree tipos struct para las estructuras de datos que
almacenan datos y que son lo suficientemente pequeñas como para copiarse de forma
eficaz. Cree tipos record si lo que busca son comparaciones y análisis de similitud que
se basen en valores, y quiere usar variables de referencia, pero no copiar valores. Los
tipos record struct se crean cuando se quieren las características de los registros para
un tipo lo suficientemente pequeño como para copiarlo de forma eficaz.
Puede obtener más información sobre los registros en el artículo de referencia del
lenguaje C# para el tipo de registro y la especificación de tipo de registro propuesta y la
especificación de estructura de registro.
Tutorial: Exploración de ideas mediante
instrucciones de nivel superior para
compilar código mientras aprende
Artículo • 28/11/2022 • Tiempo de lectura: 8 minutos
" Obtener información sobre las reglas que rigen el uso de las instrucciones de nivel
superior.
" Usar instrucciones de nivel superior para explorar algoritmos.
" Refactorizar exploraciones en componentes reutilizables.
Requisitos previos
Tendrá que configurar el equipo para ejecutar .NET 6, que incluye el compilador de
C# 10. El compilador de C# 10 está disponible a partir de Visual Studio 2022 o del
SDK de .NET 6 .
En este tutorial se da por supuesto que conoce bien C# y. NET, incluidos Visual Studio o
la CLI de .NET.
Comienzo de la exploración
Las instrucciones de nivel superior permiten evitar la ceremonia adicional que requiere
colocar el punto de entrada del programa en un método estático en una clase. El punto
de partida típico de una aplicación de consola nueva es similar al código siguiente:
C#
using System;
namespace Application
class Program
Console.WriteLine("Hello World!");
C#
Console.WriteLine("Hello, World!");
) Importante
Las plantillas de C# para .NET 6 usan instrucciones de nivel superior. Es posible que
la aplicación no coincida con el código de este artículo si ya ha actualizado a
.NET 6. Para obtener más información, consulte el artículo Las nuevas plantillas de
C# generan instrucciones de nivel superior.
Microsoft.NET.Sdk
Microsoft.NET.Sdk.Web
Microsoft.NET.Sdk.Worker
Estas directivas de global using implícitas incluyen los espacios de nombres más
comunes para el tipo de proyecto.
Esta característica simplifica lo que se necesita para comenzar a explorar nuevas ideas.
Puede usar las instrucciones de nivel superior para escenarios de scripting o para
explorar. Una vez que conozca los aspectos básicos, puede empezar a refactorizar el
código y crear métodos, clases u otros ensamblados para los componentes reutilizables
que ha compilado. Las instrucciones de nivel superior permiten una experimentación
rápida y tutoriales para principiantes. También proporcionan una ruta fluida desde la
experimentación hasta la obtención de programas completos.
C#
Console.WriteLine(args);
No declare una variable args . Para el único archivo de código fuente que contiene las
instrucciones de nivel superior, el compilador reconoce args para indicar los
argumentos de línea de comandos. El tipo de args es string[] , como en todos los
programas de C#.
CLI de .NET
Consola
System.String[]
C#
Console.WriteLine();
foreach(var s in args)
Console.Write(s);
Console.Write(' ');
Console.WriteLine();
C#
string[] answers =
"Without a doubt.", "Better not tell you now.", "My sources say
no.",
"You may rely on it.", "Concentrate and ask again.", "Very doubtful.",
"Most likely.",
"Outlook good.",
"Yes.",
};
Esta matriz tiene diez respuestas que son afirmativas, cinco inexpresivas y cinco
negativas. A continuación, agregue el código siguiente para generar y mostrar una
respuesta aleatoria de la matriz:
C#
Console.WriteLine(answers[index]);
Puede volver a ejecutar la aplicación para ver los resultados. Debería ver algo parecido a
la salida siguiente:
CLI de .NET
Este código responde a las preguntas, pero agreguemos una característica más. Quiere
que la aplicación de preguntas simule que se piensa la respuesta. Puede hacerlo si
agrega una animación de ASCII y se detiene mientras trabaja. Agregue el código
siguiente después de la línea que reproduce la pregunta:
C#
Console.Write("| -");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("/ \\");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("- |");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("\\ /");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.WriteLine();
También tendrá que agregar una instrucción using a la parte superior del archivo de
código fuente:
C#
using System.Threading.Tasks;
Las instrucciones using deben aparecer antes que cualquier otra del archivo. De lo
contrario, es un error del compilador. Puede volver a ejecutar el programa y ver la
animación. Eso mejora la experiencia. Experimente con la duración del retraso hasta que
le guste.
C#
Console.WriteLine();
foreach(var s in args)
Console.Write(s);
Console.Write(' ');
Console.WriteLine();
Console.Write("| -");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("/ \\");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("- |");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("\\ /");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.WriteLine();
string[] answers =
"Without a doubt.", "Better not tell you now.", "My sources say
no.",
"You may rely on it.", "Concentrate and ask again.", "Very doubtful.",
"Most likely.",
"Outlook good.",
"Yes.",
};
Console.WriteLine(answers[index]);
Puede empezar por crear una función local en el archivo. Reemplace la animación actual
por el código siguiente:
C#
await ShowConsoleAnimation();
Console.Write("| -");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("/ \\");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("- |");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("\\ /");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.WriteLine();
En el código anterior se crea una función local dentro del método Main. Todavía no es
reutilizable. Por tanto, extraiga ese código en una clase. Cree un archivo con el nombre
utilities.cs y agregue el código siguiente:
C#
namespace MyNamespace
Console.Write("| -");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("/ \\");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("- |");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.Write("\\ /");
await Task.Delay(50);
Console.Write("\b\b\b");
Console.WriteLine();
Un archivo que tiene instrucciones de nivel superior también puede contener espacios
de nombres y tipos al final del archivo, después de las instrucciones de nivel superior.
Pero para este tutorial, coloque el método de animación en un archivo independiente
para que sea más fácil de usar.
C#
foreach (string s in new[] { "| -", "/ \\", "- |", "\\ /", })
Console.Write(s);
await Task.Delay(50);
Console.Write("\b\b\b");
Ahora tiene una aplicación completa y ha refactorizado los elementos reutilizables para
su uso posterior. Puede llamar al nuevo método de utilidad desde las instrucciones de
nivel superior, tal como se muestra a continuación en la versión finalizada del programa
principal:
C#
using MyNamespace;
Console.WriteLine();
foreach(var s in args)
Console.Write(s);
Console.Write(' ');
Console.WriteLine();
await Utilities.ShowConsoleAnimation();
string[] answers =
"Without a doubt.", "Better not tell you now.", "My sources say
no.",
"You may rely on it.", "Concentrate and ask again.", "Very doubtful.",
"Most likely.",
"Outlook good.",
"Yes.",
};
Console.WriteLine(answers[index]);
Resumen
Las instrucciones de nivel superior facilitan la creación de programas sencillos para
explorar nuevos algoritmos. Puede experimentar con algoritmos si prueba otros
fragmentos de código. Una vez que haya aprendido lo que funciona, puede refactorizar
el código para que sea más fácil de mantener.
Requisitos previos
Deberá configurar la máquina para ejecutar .NET 5, incluido el compilador de C# 9. El
compilador de C# 9 está disponible a partir de la versión 16.8 de Visual Studio 2019 o
del SDK de .NET 5 .
Es posible compilar una clase de C# que modele este comportamiento. Una clase
CanalLock podría admitir los comandos para abrir o cerrar cualquiera de las
compuertas. Tendría otros comandos para aumentar o reducir el nivel del agua. La clase
también debería admitir propiedades para leer el estado actual de ambas compuertas y
el nivel del agua. Los métodos implementan las medidas de seguridad.
C#
Low,
High
El código anterior inicializa el objeto de manera que ambas compuertas estén cerradas y
el nivel del agua sea bajo. Luego, escriba el código de prueba siguiente en el método
Main como guía para crear una primera implementación de la clase:
C#
Console.WriteLine(canalGate);
canalGate.SetLowGate(open: true);
canalGate.SetLowGate(open: false);
canalGate.SetWaterLevel(WaterLevel.High);
canalGate.SetHighGate(open: true);
canalGate.SetHighGate(open: false);
canalGate.SetWaterLevel(WaterLevel.Low);
canalGate.SetLowGate(open: true);
canalGate.SetLowGate(open: false);
C#
HighWaterGateOpen = open;
LowWaterGateOpen = open;
CanalLockWaterLevel = newLevel;
C#
Console.WriteLine("=============================================");
try
canalGate.SetHighGate(open: true);
catch (InvalidOperationException)
{
HighWaterGateOpen = true;
Las pruebas se realizan correctamente. Pero a medida que agrega más pruebas,
agregará cada vez más cláusulas if y probará distintas propiedades. Pronto, estos
métodos resultarán demasiado complicados a medida que agregue más condicionales.
La cuarta y la última fila de la tabla tienen tachado el texto porque no son válidas. El
código que va a agregar ahora debe garantizar que la compuerta superior no se abrirá
nunca si el nivel del agua es bajo. Esos estados se pueden codificar como una expresión
switch única (recuerde que false indica "Cerrada"):
C#
HighWaterGateOpen = (open, HighWaterGateOpen, CanalLockWaterLevel) switch
};
Pruebe esta versión. Las pruebas se realizan correctamente, lo que valida el código. En la
tabla completa se muestran las combinaciones posibles de entradas y resultados. Eso
significa que tanto usted como otros desarrolladores pueden examinar rápidamente la
tabla y ver que se han cubierto todas las entradas posibles. Usar el compilador puede
hacerlo más fácil. Después de agregar el código anterior, puede ver que el compilador
genera una advertencia: CS8524 indica que la expresión switch no cubre todas las
entradas posibles. El motivo de esta advertencia es que una de las entradas es de tipo
enum . El compilador interpreta "todas las entradas posibles" como todas las entradas del
tipo subyacente, por lo general, un int . Esta expresión switch solo comprueba los
valores declarados en la enum . Para quitar la advertencia, puede agregar un patrón de
descarte comodín para el último segmento de la expresión. Esta condición genera una
excepción porque indica una entrada no válida:
C#
El primer cambio consiste en combinar todos los segmentos en los que el comando va a
cerrar la compuerta; eso siempre se permite. Agregue el código siguiente como el
primer segmento de la expresión switch:
C#
Luego, puede simplificar los cuatro segmentos en los que el comando indica abrir la
compuerta. En ambos casos en los que el nivel del agua es alto, se puede abrir la
compuerta. (En un caso, ya está abierta). Un caso en el que el nivel del agua es bajo
genera una excepción y el otro no debería ocurrir. Debería ser seguro generar la misma
excepción si el cierre hidráulico ya tiene un estado no válido. Puede hacer estas
simplificaciones para esos segmentos:
C#
Vuelva a ejecutar las pruebas y las completarán correctamente. Esta es la versión final
del método SetHighGate :
C#
};
C#
Console.WriteLine();
Console.WriteLine();
try
canalGate.SetWaterLevel(WaterLevel.High);
canalGate.SetLowGate(open: true);
catch (InvalidOperationException)
{
Console.WriteLine();
Console.WriteLine();
try
canalGate.SetLowGate(open: true);
canalGate.SetWaterLevel(WaterLevel.High);
catch (InvalidOperationException)
{
Console.WriteLine();
Console.WriteLine();
try
canalGate.SetWaterLevel(WaterLevel.High);
canalGate.SetHighGate(open: true);
canalGate.SetWaterLevel(WaterLevel.Low);
catch (InvalidOperationException)
{
Vuelva a ejecutar la aplicación. Puede ver que se generan errores en las pruebas nuevas
y que la esclusa de canal queda en un estado no válido. Intente implementar usted
mismo el resto de los métodos. El método para establecer la compuerta inferior debe
ser similar al que se usa para establecer la compuerta superior. El método que cambia el
nivel del agua tiene otras comprobaciones, pero debe seguir una estructura similar.
Puede que le resulte útil usar el mismo proceso para el método que establece el nivel
del agua. Comience con las cuatro entradas: El estado de ambas compuertas, el estado
actual del nivel del agua y el nuevo nivel del agua solicitado. La expresión switch debe
empezar por:
C#
// elided
};
C#
};
};
Resumen
En este tutorial, aprendió a usar la coincidencia de patrones para comprobar el estado
interno de un objeto antes de aplicar cualquier cambio en ese estado. Puede comprobar
combinaciones de propiedades. Una vez que cree tablas para cualquiera de esas
transiciones, probará el código y, luego, lo simplificará para su lectura y mantenimiento.
Estas refactorizaciones iniciales pueden sugerir otras refactorizaciones que validen el
estado interno o administren otros cambios de la API. En este tutorial se combinan
clases y objetos con un enfoque más orientado a datos basado en patrones para
implementar esas clases.
Tutorial: Actualización de interfaces con
métodos de interfaz predeterminados
Artículo • 28/11/2022 • Tiempo de lectura: 6 minutos
Requisitos previos
Deberá configurar la máquina para ejecutar .NET, incluido el compilador de C#. El
compilador de C# está disponible con Visual Studio 2022 o el SDK de .NET .
C#
C#
En esas interfaces, el equipo pudo generar una biblioteca para sus usuarios con el fin de
crear una mejor experiencia para los clientes. Su objetivo era crear una relación más
estrecha con los clientes existentes y mejorar sus relaciones con los clientes nuevos.
La forma más natural para agregar esta funcionalidad es mejorar la interfaz ICustomer
con un método para aplicar los descuentos por fidelización. Esta sugerencia de diseño
es motivo de preocupación entre los desarrolladores experimentados: "Las interfaces
son inmutables una vez que se han publicado. ¡Este es un cambio importante!"
Implementaciones de interfaz predeterminadas para actualizar interfaces. Los autores de
bibliotecas pueden agregar a nuevos miembros a la interfaz y proporcionar una
implementación predeterminada para esos miembros.
C#
// Version 1:
return 0.10m;
return 0;
C#
Reminders =
};
c.AddOrder(o);
c.AddOrder(o);
ICustomer theCustomer = c;
Console.WriteLine($"Current discount:
{theCustomer.ComputeLoyaltyDiscount()}");
C#
// Check the discount:
ICustomer theCustomer = c;
Console.WriteLine($"Current discount:
{theCustomer.ComputeLoyaltyDiscount()}");
de sus interfaces. Esa no ha cambiado. Para poder llamar a cualquier método declarado
e implementado en la interfaz, la variable debe ser del tipo de la interfaz: ICustomer en
este ejemplo.
Proporcionar parametrización
Ese es un buen inicio. Pero la implementación predeterminada es demasiado restrictiva.
Muchos consumidores de este sistema pueden elegir diferentes umbrales para el
número de compras, una longitud diferente de la pertenencia o un porcentaje diferente
del descuento. Puede proporcionar una mejor experiencia de actualización para más
clientes proporcionando una manera de establecer esos parámetros. Vamos a agregar
un método estático que establezca esos tres parámetros controlando la implementación
predeterminada:
C#
// Version 2:
TimeSpan ago,
length = ago;
orderCount = minimumOrders;
discountPercent = percentageDiscount;
return discountPercent;
return 0;
Las aplicaciones que usan la fórmula general para calcular el descuento por fidelidad,
pero diferentes parámetros, no necesitan proporcionar una implementación
personalizada: pueden establecer los argumentos a través de un método estático. Por
ejemplo, el siguiente código establece una "apreciación de cliente" que recompensa a
cualquier cliente con más de una pertenencia al mes:
C#
Console.WriteLine($"Current discount:
{theCustomer.ComputeLoyaltyDiscount()}");
Considere una startup que desea captar nuevos clientes. Ofrecen un descuento del 50 %
en el primer pedido de un cliente nuevo. De lo contrario, los clientes existentes obtienen
el descuento estándar. El autor de la biblioteca necesita mover la implementación
predeterminada a un método protected static para que cualquier clase que
implemente esta interfaz pueda reutilizar el código en su implementación. La
implementación predeterminada del miembro de la interfaz llama a este método
compartido así:
C#
return discountPercent;
return 0;
C#
if (PreviousOrders.Any() == false)
return 0.50m;
else
return ICustomer.DefaultLoyaltyDiscount(this);
Estas nuevas características significan que las interfaces se pueden actualizar de forma
segura cuando hay una implementación predeterminada razonable para esos nuevos
miembros. Diseñe cuidadosamente las interfaces para expresar ideas funcionales únicas
que puedan implementarse con varias clases. Esto facilita la actualización de esas
definiciones de interfaz cuando se descubren nuevos requisitos para esa misma idea
funcional.
Tutorial: Funcionalidad de combinación
al crear clases mediante interfaces con
métodos de interfaz predeterminados
Artículo • 28/11/2022 • Tiempo de lectura: 9 minutos
Requisitos previos
Deberá configurar la máquina para ejecutar .NET, incluido el compilador de C#. El
compilador de C# está disponible con Visual Studio 2022 , o con el SDK de .NET o
una versión posterior.
Diseño de la aplicación
Considere una aplicación de automatización de dispositivos del hogar. Probablemente
tenga muchos tipos diferentes de luces e indicadores que podrían usarse en toda la
casa. Cada luz debe admitir las API para encenderla y apagarla, y para notificar el estado
actual. Algunas luces e indicadores pueden admitir otras características, como:
Los miembros de interfaz predeterminados son una solución mejor para este escenario
que los métodos de extensión. Los autores de clases pueden controlar qué interfaces
deciden implementar. Las interfaces que elijan están disponibles como métodos.
Además, dado que los métodos de interfaz predeterminados son virtuales de forma
predeterminada, el envío del método siempre elige la implementación en la clase.
Creación de interfaces
Empiece por crear la interfaz que define el comportamiento de todas las luces:
C#
void SwitchOn();
void SwitchOff();
bool IsOn();
Un accesorio básico de luces de techo puede implementar esta interfaz, tal como se
muestra en el código siguiente:
C#
En este tutorial, el código no dispone de dispositivos IoT, pero emula esas actividades
escribiendo mensajes en la consola. Puede explorar el código sin automatizar los
dispositivos de hogar.
A continuación, vamos a definir la interfaz para una luz que se pueda apagar
automáticamente después de un tiempo de espera:
C#
Podría agregar una implementación básica a la luz de techo, pero una solución mejor es
modificar esta definición de interfaz para proporcionar una implementación
predeterminada virtual :
C#
public interface ITimerLight : ILight
SwitchOn();
await Task.Delay(duration);
SwitchOff();
C#
C#
Off,
On,
TimerModeOn
state = HalogenLightState.TimerModeOn;
await Task.Delay(duration);
state = HalogenLightState.Off;
Funcionalidades de combinación
Las ventajas de los métodos de interfaz predeterminados resultan más claras a medida
que se introducen funcionalidades más avanzadas. El uso de interfaces permite
combinar las funcionalidades. También permite que cada autor de clase elija entre la
implementación predeterminada y una implementación personalizada. Vamos a agregar
una interfaz con una implementación predeterminada para una luz parpadeante:
C#
SwitchOn();
await Task.Delay(duration);
SwitchOff();
await Task.Delay(duration);
C#
C#
C#
await Task.Delay(duration);
C#
light.SwitchOn();
light.SwitchOff();
await timer.TurnOnFor(1000);
else
else
C#
await TestLightCapabilities(overhead);
Console.WriteLine();
await TestLightCapabilities(halogen);
Console.WriteLine();
await TestLightCapabilities(led);
Console.WriteLine();
await TestLightCapabilities(fancy);
Console.WriteLine();
C#
NoPower,
ACPower,
FullBattery,
MidBattery,
LowBattery
C#
void SwitchOn();
void SwitchOff();
bool IsOn();
"más cercana". Ha visto ejemplos en las clases anteriores que reemplazaron los
miembros de otras interfaces derivadas.
Evite reemplazar el mismo método en varias interfaces derivadas. Al hacerlo, se crea una
llamada de método ambiguo siempre que una clase implementa ambas interfaces
derivadas. El compilador no puede elegir un solo método mejor para que emita un
error. Por ejemplo, si tanto IBlinkingLight como ITimerLight implementaron una
invalidación de PowerStatus , OverheadLight tendría que proporcionar una invalidación
más específica. De lo contrario, el compilador no puede elegir entre las
implementaciones en las dos interfaces derivadas. Normalmente, puede evitar esta
situación al mantener las definiciones de interfaz pequeñas y centradas en una
característica. En este escenario, cada funcionalidad de una luz es su propia interfaz; solo
las clases heredan varias interfaces.
Los intervalos e índices proporcionan una sintaxis concisa para acceder a elementos
únicos o intervalos en una secuencia.
Esta compatibilidad con lenguajes se basa en dos nuevos tipos y dos nuevos
operadores:
Comencemos con las reglas de los índices. Considere un elemento sequence de matriz.
El índice 0 es igual que sequence[0] . El índice ^0 es igual que
sequence[sequence.Length] . La expresión sequence[^0] produce una excepción, al igual
C#
"The", // 0 ^9
"quick", // 1 ^8
"brown", // 2 ^7
"fox", // 3 ^6
"jumps", // 4 ^5
"over", // 5 ^4
"the", // 6 ^3
"lazy", // 7 ^2
"dog" // 8 ^1
}; // 9 (or words.Length) ^0
C#
El siguiente código crea un subrango con las palabras "quick", "brown" y "fox". Va de
words[1] a words[3] . El elemento words[4] no se encuentra en el intervalo.
C#
Console.WriteLine();
El código siguiente devuelve el rango con "lazy" y "dog". Incluye words[^2] y words[^1] .
El índice del final words[^0] no se incluye. Agregue el código siguiente también:
C#
Console.WriteLine();
En los ejemplos siguientes se crean rangos con final abierto para el inicio, el final o
ambos:
C#
string[] allWords = words[..]; // contains "The" through "dog".
Console.WriteLine();
Console.WriteLine();
Console.WriteLine();
También puede declarar rangos o índices como variables. La variable se puede usar
luego dentro de los caracteres [ y ] :
C#
Console.WriteLine(words[the]);
Console.WriteLine();
El ejemplo siguiente muestra muchos de los motivos para esas opciones. Modifique x ,
y y z para probar diferentes combinaciones. Al experimentar, use valores donde x sea
menor que y y y sea menor que z para las combinaciones válidas. Agregue el código
siguiente a un nuevo método. Pruebe diferentes combinaciones:
C#
int x = 12;
int y = 25;
int z = 36;
No solo las matrices admiten índices y rangos. También puede usar índices y rangos con
string, Span<T> o ReadOnlySpan<T>.
C#
if (implicitRange.Equals(explicitRange))
Console.WriteLine(
// Sample output:
) Importante
Las conversiones implícitas de Int32 a inician Index cuando
ArgumentOutOfRangeException el valor es negativo. Del mismo modo, el Index
constructor produce una ArgumentOutOfRangeException excepción cuando el value
parámetro es negativo.
Cualquier tipo que proporcione un indexador con un parámetro Index o Range admite
de manera explícita índices o rangos, respectivamente. Un indexador que toma un único
parámetro Range puede devolver un tipo de secuencia diferente, como
System.Span<T>.
) Importante
El rendimiento del código que usa el operador de rango depende del tipo del
operando de la secuencia.
Por ejemplo, los tipos de .NET siguientes admiten tanto índices como rangos: String,
Span<T> y ReadOnlySpan<T>. List<T> admite índices, pero no rangos.
Array tiene un comportamiento con más matices. Así, las matrices de una sola
dimensión admiten índices y rangos, Las matrices multidimensionales no admiten
indexadores o rangos. El indexador de una matriz multidimensional tiene varios
parámetros, no un parámetro único. Las matrices escalonadas, también denominadas
matriz de matrices, admiten tanto intervalos como indexadores. En el siguiente ejemplo
se muestra cómo iterar por una subsección rectangular de una matriz escalonada. Se
itera por la sección del centro, excluyendo la primera y las últimas tres filas, así como la
primera y las dos últimas columnas de cada fila seleccionada:
C#
new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 },
};
Console.Write($"{cell}, ");
Console.WriteLine();
En todos los casos, el operador de rango para Array asigna una matriz para almacenar
los elementos devueltos.
Escenarios para los índices y los rangos
A menudo usará rangos e índices cuando quiera analizar una parte de una secuencia
más grande. La nueva sintaxis es más clara al leer exactamente qué parte de la
secuencia está implicada. La función local MovingAverage toma un Range como su
argumento. El método enumera solo ese rango al calcular el mínimo, el máximo y la
media. Pruebe con el código siguiente en su proyecto:
C#
Range r = start..(start+10);
subSequence[range].Min(),
subSequence[range].Max(),
subSequence[range].Average()
);
Por ejemplo:
C#
Console.WriteLine(string.Join(",", firstThreeItems));
Console.WriteLine(string.Join(",", arrayOfFiveItems));
// output:
// 11,2,3
// 1,2,3,4,5
Los tipos de referencia que aceptan valores NULL complementan los tipos de referencia
de la misma manera que los tipos de valor que aceptan valores NULL complementan los
tipos de valor. Declarará una variable para que sea un tipo de referencia que acepta
valores NULL anexando un elemento ? al tipo. Por ejemplo, string? representa un
elemento string que acepta valores NULL. Puede utilizar estos nuevos tipos para
expresar más claramente la intención del diseño: algunas variables siempre deben tener
un valor, y a otras les puede faltar un valor.
" Incorporar los tipos de referencia que aceptan valores NULL y que no aceptan
valores NULL en los diseños
" Habilitar las comprobaciones de tipos de referencia que aceptan valores NULL en
todo el código
" Escribir código en la parte en la que el compilador aplica esas decisiones de diseño
" Usar la característica de referencia que acepta valores NULL en sus propios diseños
Requisitos previos
Deberá configurar la máquina para ejecutar .NET, incluido el compilador de C#. El
compilador de C# está disponible con Visual Studio 2022 o el SDK de .NET .
En este tutorial se da por supuesto que conoce bien C# y. NET, incluidos Visual Studio o
la CLI de .NET.
El código que escriba para este ejemplo expresa dicha intención y el compilador la
exige.
que se haya creado la aplicación, se deberá especificar que todo el proyecto se compila
en un contexto de anotaciones que admite un valor NULL habilitado. Abra el archivo
.csproj y agregue un elemento Nullable al elemento PropertyGroup . Establezca su valor
en enable . Debe participar en la característica de tipos de referencia que aceptan
valores NULL en proyectos anteriores a C# 11. El motivo es que, una vez que la
característica está activada, las declaraciones de variables de referencia existentes se
convierten en tipos de referencia que no aceptan valores NULL. Aunque esa decisión lo
ayudará a detectar problemas donde el código existente puede no tener
comprobaciones de valores NULL adecuadas, es posible que no se refleje con precisión
la intención del diseño original:
XML
<Nullable>enable</Nullable>
Estos tipos usarán ambas los tipos de referencia que aceptan valores NULL y los que no
aceptan valores NULL para expresar qué miembros que son necesarios y cuáles
opcionales. Los tipos de referencia que aceptan valores NULL comunican claramente esa
intención de diseño:
Las preguntas que forman parte de la encuesta nunca pueden ser NULL: no tiene
sentido formular una pregunta vacía.
Los encuestados nunca pueden aceptar valores NULL. Quiere realizar un
seguimiento de las personas contactadas, incluso de los encuestados que han
rechazado participar.
Cualquier respuesta a una pregunta puede tener valores NULL. Los encuestados
pueden negarse a responder a algunas preguntas o a todas.
Si ha programado en C#, puede estar tan acostumbrado a los tipos de referencia que
permiten valores null que puede haber perdido otras oportunidades para declarar
instancias que no admiten un valor NULL:
A medida que escriba el código, verá que un tipo de referencia que no admite un valor
NULL, como el predeterminado para las referencias, evita errores comunes que podrían
generar NullReferenceException. Una lección de este tutorial es que tomó decisiones
sobre qué variables podrían ser o no null . El lenguaje no proporcionó una sintaxis para
expresar esas decisiones. Ahora sí.
namespace NullableIntroduction
C#
namespace NullableIntroduction
YesNo,
Number,
Text
C#
namespace NullableIntroduction;
YesNo,
Number,
Text
A continuación, cree una clase public denominada " SurveyRun ". Esta clase contiene una
lista de objetos SurveyQuestion y métodos para agregar preguntas a la encuesta, tal
como se muestra en el código siguiente:
C#
using System.Collections.Generic;
namespace NullableIntroduction
Al igual que antes, debe inicializar el objeto de lista en un valor distinto a NULL o el
compilador emitirá una advertencia. No hay ninguna comprobación de valores que
aceptan valores NULL en la segunda sobrecarga de AddQuestion porque no son
necesarias: ha declarado esa variable para que no acepte valores NULL. Su valor no
puede ser null .
C#
var surveyRun = new SurveyRun();
Dado que todo el proyecto está habilitado para un contexto de anotaciones que admite
un valor NULL, al pasar null a cualquier método que espera un tipo de referencia que
no admite un valor NULL, recibirá una advertencia. Pruébelo agregando la siguiente
línea a Main :
C#
surveyRun.AddQuestion(QuestionType.Text, default);
Necesitará una clase para representar una respuesta de encuesta, así que agréguela en
este momento. Habilite la compatibilidad con la aceptación de valores NULL. Agregue
una propiedad Id y un constructor que la inicialice, tal como se muestra en el código
siguiente:
C#
namespace NullableIntroduction
C#
C#
if (ConsentToSurvey())
int index = 0;
if (answer != null)
surveyResponses.Add(index, answer);
index++;
switch (question.TypeOfQuestion)
case QuestionType.YesNo:
case QuestionType.Number:
n = randomGenerator.Next(-30, 101);
case QuestionType.Text:
default:
case 0:
return default;
case 1:
return "Red";
case 2:
return "Green";
case 3:
return "Blue";
Al usar null en las respuestas que faltan se resalta un punto clave para trabajar con
tipos de referencia que aceptan valores NULL: el objetivo no es quitar todos los valores
null del programa. En cambio, de lo que se trata es de garantizar que el código que
escribe expresa la intención del diseño. Los valores que faltan son un concepto
necesario para expresar en el código. El valor null es una forma clara de expresar los
valores que faltan. El intento de quitar todos los valores null solo lleva a definir alguna
otra forma de expresar esos valores que faltan sin null .
C#
private List<SurveyResponse>? respondents;
int respondentsConsenting = 0;
if (respondent.AnswerSurvey(surveyQuestions))
respondentsConsenting++;
respondents.Add(respondent);
El último paso para ejecutar la encuesta es agregar una llamada al realizar la encuesta al
final del método Main :
C#
surveyRun.PerformSurvey(50);
C#
Dado que surveyResponses es un tipo de referencia que admite un valor NULL, las
comprobaciones de valores NULL son necesarias antes de desreferenciarlo. El método
Answer devuelve una cadena que no admite un valor NULL, por lo que tenemos que
cubrir el caso de que falte una respuesta mediante el operador de fusión de NULL.
A continuación, agregue estos tres miembros con forma de expresión a la clase
SurveyRun :
C#
C#
Console.WriteLine($"Participant: {participant.Id}:");
if (participant.AnsweredSurvey)
Console.WriteLine($"\t{surveyRun.GetQuestion(i).QuestionText} :
{answer}");
else
Console.WriteLine("\tNo responses");
Pasos siguientes
Obtenga información sobre cómo usar tipos de referencia que aceptan valores NULL al
utilizar Entity Framework:
Aspectos básicos de Entity Framework Core: trabajo con tipos de referencia que
aceptan valores NULL
Tutorial: Generación y uso de secuencias
asincrónicas con C# y .NET
Artículo • 20/02/2023 • Tiempo de lectura: 10 minutos
Requisitos previos
Deberá configurar el equipo para que ejecute .NET, incluido el compilador de C#. El
compilador de C# está disponible con Visual Studio 2022 o el SDK de .NET .
Deberá crear un token de acceso de GitHub para poder tener acceso al punto de
conexión de GraphQL de GitHub. Seleccione los siguientes permisos para el token de
acceso de GitHub:
repo:status
public_repo
Guarde el token de acceso en un lugar seguro para usarlo a fin de obtener acceso al
punto de conexión de API de GitHub.
2 Advertencia
En este tutorial se da por supuesto que conoce bien C# y. NET, incluidos Visual Studio o
la CLI de .NET.
Ejecución de la aplicación de inicio
Puede obtener el código para la aplicación de inicio usada en este tutorial en el
repositorio dotnet/docs de la carpeta csharp/whats-new/tutorials .
C#
// https://help.github.com/articles/creating-a-personal-access-token-
for-the-command-line/#creating-a-token
// - repo:status
// - public_repo
// Replace the 3rd parameter to the following code with your GitHub
access token.
"");
};
});
try
cancellationSource.Token, progressReporter);
Console.WriteLine(issue);
catch (OperationCanceledException)
Puede establecer una variable de entorno GitHubKey para el token de acceso personal, o
bien puede reemplazar el último argumento en la llamada a GetEnvVariable por el
token de acceso personal. No coloque el código de acceso en el código fuente si va a
compartir el origen con otros usuarios. No cargue nunca códigos de acceso en un
repositorio de código fuente compartido.
Examen de la implementación
La implementación revela por qué observó el comportamiento descrito en la sección
anterior. Examine el código de RunPagedQueryAsync :
C#
Query = queryText
};
issueAndPRQuery.Variables["repo_name"] = repoName;
int pagesReturned = 0;
int issuesReturned = 0;
JObject results =
JObject.Parse(response.HttpResponse.Body.ToString()!);
hasMorePages = (bool)pageInfo(results)["hasPreviousPage"]!;
issueAndPRQuery.Variables["start_cursor"] = pageInfo(results)
["startCursor"]!.ToString();
issuesReturned += issues(results)["nodes"]!.Count();
finalResults.Merge(issues(results)["nodes"]!);
progress?.Report(issuesReturned);
cancel.ThrowIfCancellationRequested();
return finalResults;
Hay varios elementos en este código que se pueden mejorar. Lo más importante,
RunPagedQueryAsync debe asignar el almacenamiento para todas las incidencias
devueltas. Este ejemplo se detiene en 250 incidencias porque la recuperación de todas
las incidencias abiertas requeriría mucha más memoria para almacenar todas las
incidencias recuperadas. Los protocolos para admitir los informes de progreso y la
cancelación hacen que el algoritmo sea más difícil de comprender en su primera lectura.
Hay más tipos y API implicados. Debe realizar un seguimiento de las comunicaciones a
través de CancellationTokenSource y su CancellationToken asociado para comprender
dónde se solicita la cancelación y dónde se concede.
Estas nuevas características del lenguaje dependen de tres nuevas interfaces agregadas
a .NET Standard 2.1 e implementadas en .NET Core 3.0:
System.Collections.Generic.IAsyncEnumerable<T>
System.Collections.Generic.IAsyncEnumerator<T>
System.IAsyncDisposable
System.Collections.Generic.IEnumerable<T>
System.Collections.Generic.IEnumerator<T>
System.IDisposable
C#
El código de inicio procesa cada página a medida que se recupera, tal como se muestra
en el código siguiente:
C#
finalResults.Merge(issues(results)["nodes"]!);
progress?.Report(issuesReturned);
cancel.ThrowIfCancellationRequested();
C#
Ha terminado los cambios para generar una secuencia asincrónica. El método finalizado
debería ser similar al código siguiente:
C#
Query = queryText
};
issueAndPRQuery.Variables["repo_name"] = repoName;
int pagesReturned = 0;
int issuesReturned = 0;
JObject results =
JObject.Parse(response.HttpResponse.Body.ToString()!);
hasMorePages = (bool)pageInfo(results)["hasPreviousPage"]!;
issueAndPRQuery.Variables["start_cursor"] = pageInfo(results)
["startCursor"]!.ToString();
issuesReturned += issues(results)["nodes"]!.Count();
C#
});
try
cancellationSource.Token, progressReporter);
Console.WriteLine(issue);
}
catch (OperationCanceledException)
C#
int num = 0;
Console.WriteLine(issue);
C#
int num = 0;
try
Console.WriteLine(issue);
Console.WriteLine($"Received {++num} issues in total");
} finally
if (enumerator != null)
await enumerator.DisposeAsync();
C#
Query = queryText
};
issueAndPRQuery.Variables["repo_name"] = repoName;
int pagesReturned = 0;
int issuesReturned = 0;
JObject results =
JObject.Parse(response.HttpResponse.Body.ToString()!);
hasMorePages = (bool)pageInfo(results)["hasPreviousPage"]!;
issueAndPRQuery.Variables["start_cursor"] = pageInfo(results)
["startCursor"]!.ToString();
issuesReturned += issues(results)["nodes"]!.Count();
C#
int num = 0;
.WithCancellation(cancellation.Token))
Console.WriteLine(issue);
Console.WriteLine($"Received {++num} issues in total");
Ejecute las aplicaciones de inicio y finalizada y podrá ver las diferencias entre las
implementaciones personalmente. Puede eliminar el token de acceso de GitHub que
creó cuando inició este tutorial cuando haya terminado. Si un atacante obtuviera acceso
a dicho token, podría tener acceso a sus API de GitHub con sus credenciales.
En este tutorial, ha usado flujos asincrónicos para leer elementos individuales de una API
de red que devuelve páginas de datos. Los flujos asincrónicos también pueden leer
"flujos que nunca terminan", como un teletipo de bolsa o un sensor. La llamada a
MoveNextAsync devuelve el siguiente elemento en cuanto está disponible.
Tutorial: Escritura de un controlador de
interpolación de cadenas personalizado
Artículo • 29/11/2022 • Tiempo de lectura: 12 minutos
Prerrequisitos
Deberá configurar la máquina para ejecutar .NET 6, incluido el compilador de C# 10. El
compilador de C# 10 está disponible a partir de Visual Studio 2022 o del SDK de
.NET 6 .
En este tutorial se da por supuesto que conoce bien C# y. NET, incluidos Visual Studio o
la CLI de .NET.
Nuevo esquema
C# 10 agrega compatibilidad con un controlador de cadenas interpoladas personalizado.
Un controlador de cadenas interpoladas es un tipo que procesa la expresión del
marcador de posición en una cadena interpolada. Sin un controlador personalizado, los
marcadores de posición se procesan de forma similar a String.Format. Cada marcador
de posición tiene formato de texto y, luego, los componentes se concatenan para
formar la cadena resultante.
Puede escribir un controlador para cualquier escenario en el que use información sobre
la cadena resultante. ¿Se usará? ¿Qué restricciones hay en el formato? Estos son algunos
ejemplos:
Es posible que no necesite que ninguna de las cadenas resultantes sea mayor de
algún límite, por ejemplo, 80 caracteres. Puede procesar las cadenas interpoladas
para rellenar un búfer de longitud fija y detener el procesamiento una vez que se
alcanza esa longitud del búfer.
Puede tener un formato tabular y cada marcador de posición debe tener una
longitud fija. Un controlador personalizado puede aplicar esto, en lugar de obligar
a que todo el código de cliente se ajuste.
Implementación inicial
Partiremos de una clase Logger básica que admite distintos niveles:
C#
Off,
Critical,
Error,
Warning,
Information,
Trace
Console.WriteLine(msg);
Este elemento Logger admite seis niveles diferentes. Cuando un mensaje no pasa el
filtro de nivel de registro, no hay ninguna salida. La API pública del registrador acepta
una cadena (con formato completo) como mensaje. Ya se ha realizado todo el trabajo
para crear la cadena.
Implementación del patrón del controlador
Este paso consiste en crear un controlador de cadenas interpoladas que vuelva a crear el
comportamiento actual. Un controlador de cadenas interpoladas es un tipo que debe
tener las siguientes características:
Tener aplicado
System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute al tipo.
Un constructor que tenga dos parámetros int : literalLength y formatCount . (Se
permiten más parámetros).
Un método AppendLiteral público con la signatura public void
AppendLiteral(string s) .
C#
[InterpolatedStringHandler]
StringBuilder builder;
builder.Append(s);
builder.Append(t?.ToString());
Ahora puede agregar una sobrecarga a LogMessage en la clase Logger para probar el
nuevo controlador de cadenas interpoladas:
C#
Console.WriteLine(builder.GetFormattedText());
C#
PowerShell
A continuación, use el campo para que el controlador solo anexe literales u objetos con
formato cuando se use la cadena final:
C#
if (!enabled) return;
builder.Append(s);
if (!enabled) return;
builder.Append(t?.ToString());
C#
Console.WriteLine(builder.GetFormattedText());
Este atributo especifica la lista de argumentos para LogMessage que se asignan a los
parámetros que siguen a los parámetros literalLength y formattedCount necesarios. La
cadena vacía (""), especifica el receptor. El compilador sustituye el valor del objeto
Logger que representa this por el siguiente argumento para el constructor del
controlador. El compilador sustituye el valor de level por el siguiente argumento.
Puede proporcionar cualquier número de argumentos para cualquier controlador que
escriba. Los argumentos que agregue son argumentos de cadena.
Puede ejecutar esta versión con el mismo código de prueba. Esta vez, verá los siguientes
resultados:
PowerShell
Puede ver que se llama a los métodos AppendLiteral y AppendFormat , pero no están
haciendo nada. El controlador ha determinado que la cadena final no será necesaria, por
lo que el controlador no la compila. Todavía hay que realizar un par de mejoras.
C#
builder.Append(t?.ToString(format, null));
C#
PowerShell
Error Level. CurrentTime: 10/20/2021 12:18:29 PM. The time doesn't use
formatting.
Puede realizar una actualización final del constructor del controlador que mejore la
eficacia. El controlador puede agregar un parámetro out bool final. Establecer ese
parámetro en false indica que no se debe llamar al controlador para procesar la
expresión de cadena interpolada:
C#
Ese cambio significa que puede quitar el campo enabled . Para cambiar el tipo de valor
devuelto de AppendLiteral y AppendFormatted a void .
Ahora, al ejecutar el ejemplo, verá
el resultado siguiente:
PowerShell
Error Level. CurrentTime: 10/20/2021 12:19:10 PM. The time doesn't use
formatting.
C#
int index = 0;
int numberOfIncrements = 0;
Console.WriteLine(level);
numberOfIncrements += 5;
Puede ver que la variable index se incrementa cinco veces con cada iteración del bucle.
Dado que los marcadores de posición solo se evalúan para los niveles Critical , Error y
Warning , no para Information y Trace , el valor final de index no cumple la expectativa:
PowerShell
Critical
Error
Warning
Information
Trace
En este tutorial se supone que cuenta con una máquina que puede usar para el
desarrollo. El tutorial de .NET Hola mundo en 10 minutos cuenta con instrucciones
para configurar el entorno de desarrollo local en Windows, Linux o macOS. También
puede completar la versión interactiva de este tutorial en el explorador.
CLI de .NET
Este comando crea una nueva aplicación de consola de .NET Core en el directorio actual.
su nombre:
C#
Este sencillo ejemplo contiene los dos elementos que debe tener cada cadena
interpolada:
Un literal de cadena que empieza con el carácter $ antes del carácter de comillas
de apertura. No puede haber ningún espacio entre el símbolo $ y el carácter de
comillas. (Si quiere saber qué pasa si incluye uno, inserte un espacio después del
carácter $ , guarde el archivo y vuelva a ejecutar el programa escribiendo dotnet
run en la ventana de la consola. El compilador de C# mostrará un mensaje de
error: “error CS1056: Carácter inesperado ‘$’”).
Probemos algunos ejemplos más de interpolación de cadenas con otros tipos de datos.
C#
Luego se crea una instancia de la clase Vegetable denominada item al usar el operador
new y al proporcionar un parámetro de nombre para el constructor Vegetable :
C#
var item = new Vegetable("eggplant");
Por último, se incluye la variable item en una cadena interpolada que también contiene
un valor DateTime, un valor Decimal y un valor de enumeración Unit . Reemplace todo el
código de C# en el editor con el código siguiente y, después, use el comando dotnet
run para ejecutarlo:
C#
using System;
C#
Especifique una cadena de formato al colocar dos puntos (":") después de la expresión
de interpolación y la cadena de formato. "d" es una cadena de formato de fecha y hora
estándar que representa el formato de fecha corta. "C2" es una cadena de formato
numérica estándar que representa un número como un valor de moneda con dos
dígitos después del separador decimal.
C#
using System;
using System.Collections.Generic;
};
Console.WriteLine();
Console.WriteLine($"|{"Author",-25}|{"Title",30}|");
Console.WriteLine($"|{title.Key,-25}|{title.Value,30}|");
Los nombres de los autores están alineados a la izquierda y los títulos que escribieron
están alineados a la derecha. Para especificar la alineación, se agrega una coma (",")
después de una expresión de interpolación y se designa el ancho de campo mínimo. Si
el valor especificado es un número positivo, el campo se alinea a la derecha. Si es un
número negativo, el campo se alinea a la izquierda.
C#
Console.WriteLine($"|{"Author",25}|{"Title",30}|");
Console.WriteLine($"|{title.Key,25}|{title.Value,30}|");
C#
Consola
En este tutorial se explica cómo usar la interpolación de cadenas para dar formato a
resultados de expresión e incluirlos en una cadena de resultado. En los ejemplos se da
por hecho que ya está familiarizado con los conceptos básicos de C# y el formato de
tipos .NET. Si no conoce la interpolación de cadenas o el formato de tipos .NET, vea
antes el tutorial de interpolación de cadenas interactivo. Para más información sobre
cómo aplicar formato a tipos .NET, vea el tema Aplicar formato a tipos en .NET.
7 Nota
Introducción
La característica de interpolación de cadenas se basa en la característica de formato
compuesto y proporciona una sintaxis más legible y cómoda para incluir resultados de
expresiones con formato en una cadena de resultado.
C#
double a = 3;
double b = 4;
// Expected output:
Como se ilustra en el ejemplo, para incluir una expresión en una cadena interpolada hay
que meterla entre llaves:
C#
{<interpolationExpression>}
C#
{<interpolationExpression>:<formatString>}
C#
// Expected output:
Para más información, vea la sección Format String (Componente) del tema Formatos
compuestos. En esa sección encontrará vínculos a temas en los que se describen las
cadenas de formato estándar y personalizadas compatibles con los tipos base .NET.
Cómo controlar el ancho de campo y la
alineación de las expresiones de interpolación
con formato
Para especificar el ancho de campo mínimo y la alineación del resultado de expresión
con formato, coloque después de la expresión de interpolación una coma (",") y la
expresión constante:
C#
{<interpolationExpression>,<alignment>}
En caso de que haya que especificar una alineación y una cadena de formato, comience
por el componente de alineación:
C#
{<interpolationExpression>,<alignment>:<formatString>}
C#
double a = 3;
double b = 4;
Console.WriteLine($"|{"Arithmetic",NameAlignment}|{0.5 * (a +
b),ValueAlignment:F3}|");
Console.WriteLine($"|{"Geometric",NameAlignment}|{Math.Sqrt(a *
b),ValueAlignment:F3}|");
Console.WriteLine($"|{"Harmonic",NameAlignment}|{2 / (1 / a + 1 /
b),ValueAlignment:F3}|");
// Expected output:
// |Arithmetic| 3.500|
// |Geometric| 3.464|
// |Harmonic | 3.429|
Tal y como refleja la salida del ejemplo, si la longitud del resultado de expresión con
formato supera el ancho de campo especificado, se omitirá el valor de alignment.
Para más información, vea la sección Alignment (Componente) del tema Formatos
compuestos.
Para interpretar las secuencias de escape literalmente, use un literal de cadena textual.
Las cadenas textuales interpoladas comienzan por el carácter $ , seguido del carácter @ .
Puede usar los $ tokens y @ en cualquier orden: y $@"..." @$"..." son cadenas
textuales interpoladas válidas.
Para incluir una llave ("{" o "}") en una cadena de resultado, use dos llaves ("{{" o "}}").
Para más información, vea la sección Llaves de escape del tema Formatos compuestos.
C#
Console.WriteLine(stringWithEscapes);
Console.WriteLine(verbatimInterpolated);
// Expected output:
// C:\Users\Jane\Documents
// C:\Users\Jane\Documents
C#
C#
System.Globalization.CultureInfo.GetCultureInfo("en-US"),
System.Globalization.CultureInfo.GetCultureInfo("en-GB"),
System.Globalization.CultureInfo.GetCultureInfo("nl-NL"),
System.Globalization.CultureInfo.InvariantCulture
};
Console.WriteLine($"{culture.Name,-10}{cultureSpecificMessage}");
C#
Console.WriteLine(messageInInvariantCulture);
Conclusión
En este tutorial se han descrito escenarios habituales en los que se usa la interpolación
de cadenas. Para más información sobre la interpolación de cadenas, vea el tema
Interpolación de cadenas. Para más información sobre cómo aplicar formato a tipos
.NET, vea los temas Aplicar formato a tipos en .NET y Formatos compuestos.
Vea también
String.Format
System.FormattableString
System.IFormattable
Cadenas
Aplicación de consola
Artículo • 14/02/2023 • Tiempo de lectura: 11 minutos
Creará una aplicación que lea un archivo de texto y refleje el contenido de ese archivo
de texto en la consola. El ritmo de la salida a la consola se ajusta para que coincida con
la lectura en voz alta. Para aumentar o reducir el ritmo, presione las teclas "<" (menor
que) o ">" (mayor que). Puede ejecutar esta aplicación en Windows, Linux, macOS o en
un contenedor de Docker.
Requisitos previos
SDK DE .NET 6
Un editor de código
Creación de la aplicación
El primer paso es crear una nueva aplicación. Abra un símbolo del sistema y cree un
nuevo directorio para la aplicación. Conviértalo en el directorio actual. Escriba el
comando dotnet new console en el símbolo del sistema. Esta acción crea los archivos de
inicio para una aplicación básica "Hola mundo".
Todo el código de la aplicación sencilla Hola mundo está en Program.cs. Abra ese
archivo con el editor de texto de su elección. Reemplace el código de Program.cs por el
código siguiente:
C#
namespace TeleprompterConsole;
Console.WriteLine("Hello World!");
En la parte superior del archivo, verá una instrucción namespace . Al igual que otros
lenguajes orientados a objetos que pueda haber usado, C# utiliza espacios de nombres
para organizar los tipos. Este programa Hola mundo no es diferente. Se puede ver que
el programa está incluido en el espacio de nombres con el nombre
TeleprompterConsole .
Luego, agregue el siguiente método a la clase Program (justo debajo del método Main ):
C#
string? line;
Hay dos elementos de la sintaxis de C# con los que podría no estar familiarizado. La
instrucción using de este método administra la limpieza de recursos. La variable que se
inicializa en la instrucción using ( reader , en este ejemplo) debe implementar la interfaz
IDisposable. Esa interfaz define un único método, Dispose , que se debe llamar cuando
sea necesario liberar el recurso. El compilador genera esa llamada cuando la ejecución
llega a la llave de cierre de la instrucción using . El código generado por el compilador
garantiza que el recurso se libera incluso si se produce una excepción desde el código
en el bloqueo definido mediante la instrucción using.
La variable reader se define mediante la palabra clave var . var define una variable local
con tipo implícito. Esto significa que el tipo de la variable viene determinado por el tipo
en tiempo de compilación del objeto asignado a la variable. Aquí, ese es el valor
devuelto por el método OpenText(String), que es un objeto StreamReader.
C#
Console.WriteLine(line);
Ejecute el programa (mediante dotnet run ) y podrá ver cada línea impresa en la
consola.
Hay dos pasos hasta esta sección. Primero, actualizará el método Iterator para devolver
palabras sueltas en lugar de líneas enteras. Para ello, son necesarias estas
modificaciones. Reemplace la instrucción yield return line; por el código siguiente:
C#
A continuación, debe modificar el modo en que se consumen las líneas del archivo y
agregar un retraso después de escribir cada palabra. Reemplace la instrucción
Console.WriteLine(line) del método Main por el bloqueo siguiente:
C#
Console.Write(line);
if (!string.IsNullOrWhiteSpace(line))
// steps.
pause.Wait();
C#
var lineLength = 0;
C#
lineLength += word.Length + 1;
lineLength = 0;
Tareas asincrónicas
En este paso final, agregará el código para escribir la salida de manera asincrónica en
una tarea, mientras se ejecuta también otra tarea para leer la entrada del usuario si
quiere aumentar o reducir la velocidad de la pantalla de texto, o detendrá la
presentación del texto por completo. Incluye unos cuantos pasos y, al final, tendrá todas
las actualizaciones que necesita. El primer paso es crear un método de devolución Task
asincrónico que represente el código que ha creado hasta el momento para leer y
visualizar el archivo.
Agregue este método a su clase Program (se toma del cuerpo del método Main ):
C#
Console.Write(word);
if (!string.IsNullOrWhiteSpace(word))
await Task.Delay(200);
Advertirá dos cambios. Primero, en el cuerpo del método, en lugar de llamar a Wait()
para esperar a que finalice una tarea de manera sincrónica, esta versión usa la palabra
clave await . Para ello, debe agregar el modificador async a la signatura del método.
Este método devuelve un objeto Task . Observe que no hay ninguna instrucción Return
que devuelva un objeto Task . En su lugar, ese objeto Task se crea mediante el código
que genera el compilador cuando usa el operador await . Puede imaginar que este
método devuelve cuando alcanza un valor de await . El valor devuelto de Task indica
que el trabajo no ha finalizado. El método se reanuda cuando se completa la tarea en
espera. Cuando se ha ejecutado hasta su finalización, el valor de Task devuelto indica
que se ha completado.
El código de llamada puede supervisar ese valor de Task
devuelto para determinar cuándo se ha completado.
C#
await ShowTeleprompter();
C#
Obtenga más información sobre el método async Main en nuestra sección de aspectos
básicos.
A continuación, debe escribir el segundo método asincrónico para leer desde la Consola
y controlar las teclas "<" (menor que), ">" (mayor que), "X" o "x". Este es el método que
agrega para esa tarea:
C#
do {
if (key.KeyChar == '>')
delay -= 10;
delay += 10;
break;
} while (true);
};
await Task.Run(work);
Esto crea una expresión lambda que representa un delegado de Action que lee una
clave de la Consola y modifica una variable local que representa el retraso que se da
cuando el usuario presiona las teclas "<" (menor que) o ">" (mayor que). El método de
delegado finaliza cuando el usuario presiona las teclas "X" o "x", que permiten al usuario
detener la presentación del texto en cualquier momento. Este método usa ReadKey()
para bloquear y esperar a que el usuario presione una tecla.
Para finalizar esta característica, debe crear un nuevo método de devolución async Task
que inicie estas dos tareas ( GetInput y ShowTeleprompter ) y también administre los
datos compartidos entre ellas.
Es hora de crear una clase que controle los datos compartidos entre estas dos tareas.
Esta clase contiene dos propiedades públicas: el retraso y una marca Done para indicar
que el archivo se ha leído completamente:
C#
namespace TeleprompterConsole;
DelayInMilliseconds = newDelay;
Done = true;
una instrucción using static en la parte superior del archivo para que pueda hacer
referencia a los métodos Min y Max sin la clase incluida o los nombres de espacio de
nombres. Una instrucción using static importa los métodos de una clase, Esto contrasta
con la instrucción using sin static , que importa todas las clases de un espacio de
nombres.
C#
C#
C#
Console.Write(word);
if (!string.IsNullOrWhiteSpace(word))
await Task.Delay(config.DelayInMilliseconds);
config.SetDone();
do {
if (key.KeyChar == '>')
config.UpdateDelay(-10);
config.UpdateDelay(10);
config.SetDone();
} while (!config.Done);
};
await Task.Run(work);
lugar de a ShowTeleprompter :
C#
await RunTeleprompter();
Conclusión
En este tutorial se han mostrado varias características en torno al lenguaje C# y las
bibliotecas .NET Core, relacionadas con el trabajo en aplicaciones de consola. Puede
partir de este conocimiento para explorar más sobre el lenguaje y las clases aquí
presentadas. Ha visto los conceptos básicos de E/S de archivo y consola, el uso con
bloqueo y sin bloqueo de la programación asincrónica basada en tareas, un paseo por
el lenguaje C# y cómo se organizan los programas en C#. También ha conocido la
interfaz de la línea de comandos y la CLI de .NET.
Para obtener más información sobre la E/S de archivo, vea E/S de archivos y secuencias.
Para obtener más información sobre el modelo de programación asincrónica que se ha
usado en este tutorial, vea Programación asincrónica basada en tareas y Programación
asincrónica.
Tutorial: Realización de solicitudes HTTP
en una aplicación de consola de .NET
mediante C#
Artículo • 05/10/2022 • Tiempo de lectura: 8 minutos
En este tutorial se crea una aplicación que emite solicitudes HTTP a un servicio REST en
GitHub. La aplicación lee información en formato JSON y convierte la respuesta JSON en
objetos de C#. La conversión de JSON en objetos de C# se conoce como deserialización.
Si prefiere seguir las explicaciones con el ejemplo final del tutorial, puede descargarlo.
Para obtener instrucciones de descarga, vea Ejemplos y tutoriales.
Requisitos previos
SDK de .NET 6.0 o versiones posteriores .
Un editor de código como Visual Studio Code , que es un editor multiplataforma
de código abierto. Puede ejecutar la aplicación de ejemplo en Windows, Linux o
macOS, o bien en un contenedor de Docker.
CLI de .NET
Este comando crea los archivos de inicio para una aplicación básica "Hola mundo".
El nombre del proyecto es "WebAPIClient".
cd WebAPIClient
CLI de .NET
dotnet run
Use la clase HttpClient para realizar solicitudes HTTP. HttpClient solo admite métodos
asincrónicos para sus API de larga duración. Por lo tanto, los pasos siguientes crean un
método asincrónico y lo llaman desde el método Main.
C#
2. Agregue una directiva using en la parte superior del archivo Program.cs para que
el compilador de C# reconozca el tipo Task:
C#
using System.Threading.Tasks;
C#
await ProcessRepositories();
Este código:
4. En la clase Program , cree una instancia estática de HttpClient para controlar las
solicitudes y las respuestas.
C#
namespace WebAPIClient
class Program
//...
C#
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new
MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json"));
var stringTask =
client.GetStringAsync("https://api.github.com/orgs/dotnet/repos");
Console.Write(msg);
Este código:
C#
using System.Net.Http;
using System.Net.Http.Headers;
CLI de .NET
dotnet run
C#
using System;
namespace WebAPIClient
El código anterior define una clase para representar el objeto JSON devuelto desde
la API de GitHub. Usará esta clase para mostrar una lista de nombres de
repositorio.
C#
var streamTask =
client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos");
var repositories = await
JsonSerializer.DeserializeAsync<List<Repository>>(await streamTask);
ahora solo las ha visto como parte de una instrucción de asignación. Los otros dos
parámetros, JsonSerializerOptions y CancellationToken , son opcionales y se
omiten en el fragmento de código.
3. Agregue código para mostrar el nombre de cada repositorio. Reemplace las líneas
donde pone:
C#
Console.Write(msg);
por el siguiente:
C#
Console.WriteLine(repo.name);
C#
using System.Collections.Generic;
using System.Text.Json;
5. Ejecutar la aplicación.
CLI de .NET
dotnet run
La salida es una lista con los nombres de los repositorios que forman parte de
.NET Foundation.
Configuración de la deserialización
1. En repo.cs, cambie la propiedad name a Name y agregue un atributo
[JsonPropertyName] para especificar cómo aparece esta propiedad en JSON.
C#
[JsonPropertyName("name")]
C#
using System.Text.Json.Serialization;
C#
Console.WriteLine(repo.Name);
4. Ejecutar la aplicación.
La salida es la misma.
Refactorizar el código
El método ProcessRepositories puede realizar el trabajo asincrónico y devolver una
colección de los repositorios. Cambie ese método para devolver List<Repository> y
mueva el código que escribe la información al método Main .
1. Cambie la signatura de ProcessRepositories para devolver una tarea cuyo
resultado sea una lista de objetos Repository :
C#
C#
var streamTask =
client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos");
var repositories = await
JsonSerializer.DeserializeAsync<List<Repository>>(await streamTask);
return repositories;
3. Modifique el método Main para capturar los resultados y escribir cada nombre de
repositorio en la consola. El método Main tiene el aspecto siguiente:
C#
Console.WriteLine(repo.Name);
4. Ejecutar la aplicación.
La salida es la misma.
[JsonPropertyName("description")]
[JsonPropertyName("html_url")]
[JsonPropertyName("homepage")]
[JsonPropertyName("watchers")]
Los tipos Uri y int tienen una funcionalidad integrada para convertir a y desde la
representación de cadena. No se necesita código adicional para deserializar desde
el formato de cadena de JSON a esos tipos de destino. Si el paquete JSON
contiene datos que no se convierten en un tipo de destino, la acción de
serialización genera una excepción.
C#
Console.WriteLine(repo.Name);
Console.WriteLine(repo.Description);
Console.WriteLine(repo.GitHubHomeUrl);
Console.WriteLine(repo.Homepage);
Console.WriteLine(repo.Watchers);
Console.WriteLine();
3. Ejecutar la aplicación.
JSON
2016-02-08T21:27:00Z
Para que una fecha y hora se represente en su zona horaria, debe escribir un método de
conversión personalizado.
C#
[JsonPropertyName("pushed_at")]
C#
Console.WriteLine(repo.LastPush);
3. Ejecutar la aplicación.
Pasos siguientes
En este tutorial, ha creado una aplicación que realiza solicitudes web y analiza los
resultados. La versión de la aplicación debe coincidir ahora con el ejemplo terminado.
Introducción
En este tutorial aprenderá varias características de .NET Core y el lenguaje C#.
Aprenderá a:
Aprenderá estas técnicas mediante la creación de una aplicación que muestra uno de
los conocimientos básicos de cualquier mago: el orden aleatorio faro . En resumen, el
orden aleatorio faro es una técnica basada en dividir la baraja exactamente por la mitad;
a continuación, el orden aleatorio intercala cada carta de cada mitad de la baraja hasta
volver a crear la original.
Los magos usan esta técnica porque cada carta está en una ubicación conocida después
de cada orden aleatorio, y el orden sigue un patrón de repetición.
Para el propósito sobre el que trata este artículo, resulta divertido ocuparnos de la
manipulación de secuencias de datos. La aplicación que se va a crear compilará una
baraja de cartas y después realizará una secuencia de órdenes aleatorios, que escribirá
cada vez la secuencia completa. También podrá comparar el orden actualizado con el
original.
Este tutorial consta de varios pasos. Después de cada paso, puede ejecutar la aplicación
y ver el progreso. También puede ver el ejemplo completo en el repositorio
dotnet/samples de GitHub. Para obtener instrucciones de descarga, vea Ejemplos y
tutoriales.
Requisitos previos
Deberá configurar la máquina para ejecutar .NET Core. Puede encontrar las
instrucciones de instalación en la página Descarga de .NET Core . Puede ejecutar esta
aplicación en Windows, Ubuntu Linux, OS X o en un contenedor de Docker. Deberá
instalar su editor de código favorito. En las siguientes descripciones se usa Visual Studio
Code , que es un editor multiplataforma de código abierto. Sin embargo, puede usar
las herramientas que le resulten más cómodas.
Crear la aplicación
El primer paso es crear una nueva aplicación. Abra un símbolo del sistema y cree un
nuevo directorio para la aplicación. Conviértalo en el directorio actual. Escriba el
comando dotnet new console en el símbolo del sistema. Esta acción crea los archivos de
inicio para una aplicación básica "Hola mundo".
C#
// Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
Ahora que tiene todas las referencias que necesitará, tenga en cuenta lo que constituye
una baraja de cartas. Habitualmente, una baraja de cartas tiene cuatro palos y cada palo
tiene trece valores. Normalmente, podría plantearse crear una clase Card directamente
del archivo bat y rellenar manualmente una colección de objetos Card . Con LINQ,
puede ser más conciso que de costumbre al tratar con la creación de una baraja de
cartas. En lugar de crear una clase Card , puede crear dos secuencias para representar
los palos y rangos, respectivamente. Podrá crear un par sencillo de métodos iterator que
generará las clasificaciones y palos como objetos IEnumerable<T> de cadenas:
C#
// Program.cs
Colóquelos debajo del método Main en el archivo Program.cs . Estos dos métodos
utilizan la sintaxis yield return para generar una secuencia mientras se ejecutan. El
compilador crea un objeto que implementa IEnumerable<T> y genera la secuencia de
cadenas conforme se solicitan.
Ahora, puede usar estos métodos iterator para crear la baraja de cartas. Insertará la
consulta LINQ en nuestro método Main . Aquí tiene una imagen:
C#
// Program.cs
from r in Ranks()
Console.WriteLine(card);
Las cláusulas múltiples from generan una salida SelectMany, que crea una única
secuencia a partir de la combinación de cada elemento de la primera secuencia con
cada elemento de la segunda secuencia. El orden es importante para nuestros
propósitos. El primer elemento de la primera secuencia de origen (palos) se combina
con todos los elementos de la segunda secuencia (clasificaciones). Esto genera las trece
cartas del primer palo. Dicho proceso se repite con cada elemento de la primera
secuencia (palos). El resultado final es una baraja de cartas ordenadas por palos,
seguidos de valores.
Es importante tener en cuenta que si decide escribir las instrucciones LINQ en la sintaxis
de consulta usada anteriormente o utilizar la sintaxis de método en su lugar, siempre es
posible pasar de una forma de sintaxis a la otra. La consulta anterior escrita en la sintaxis
de consulta puede escribirse en la sintaxis de método como:
C#
Continúe y ejecute el ejemplo que se ha creado en este punto. Mostrará todas las 52
cartas de la baraja. Puede ser muy útil ejecutar este ejemplo en un depurador para
observar cómo se ejecutan los métodos Suits() y Ranks() . Puede ver claramente que
cada cadena de cada secuencia se genera solo según sea necesario.
Manipulación del orden
Seguidamente, céntrese en cómo va a establecer el orden aleatorio de las cartas de la
baraja. El primer paso en cualquier orden aleatorio consiste en dividir la baraja en dos.
Los métodos Take y Skip que forman parte de las LINQ API le ofrecen esa característica:
Colóquelos debajo del bucle foreach :
C#
// Program.cs
from r in Ranks()
Console.WriteLine(c);
// 52 cards in a deck, so 52 / 2 = 26
Pero no existe ningún método de orden aleatorio en la biblioteca estándar que pueda
aprovechar, por lo que tendrá que escribir el suyo propio. El método de orden aleatorio
que cree mostrará varias técnicas que se utilizan con programas basados en LINQ, por
lo que cada parte de este proceso se explica en pasos.
C#
// Extensions.cs
using System;
using System.Collections.Generic;
using System.Linq;
namespace LinqFaroShuffle
C#
Puede ver la incorporación del modificador this del primer argumento al método. Esto
significa que se llama al método como si fuese un método de miembro del tipo del
primer argumento. Esta declaración de método también sigue una expresión estándar
donde los tipos de entrada y salida son IEnumerable<T> . Dicha práctica permite que los
métodos LINQ se encadenen entre sí para realizar consultas más complejas.
Naturalmente, dado que dividió la baraja en mitades, tendrá que unir esas mitades. En el
código, esto significa que enumerará las dos secuencias adquiridas a través de Take y
Skip a la vez, interleaving los elementos y crear una sola secuencia: su baraja de cartas
recién ordenada aleatoriamente. Escribir un método LINQ que funciona con dos
secuencias requiere que comprenda cómo funciona IEnumerable<T>.
C#
Ahora que ha escrito este método, vuelva al método Main y ordene la baraja
aleatoriamente una vez:
C#
// Program.cs
from r in Ranks()
Console.WriteLine(c);
Console.WriteLine(c);
Comparaciones
¿Cuántos órdenes aleatorios se necesitan para devolver la baraja a su orden original?
Para averiguarlo, debe escribir un método que determine si dos secuencias son iguales.
Cuando ya disponga del método, debe colocar el código que ordena la baraja
aleatoriamente en un bucle y comprobarlo para ver cuándo la baraja vuelve a tener su
orden original.
Debe ser sencillo escribir un método para determinar si las dos secuencias son iguales.
Presenta una estructura similar al método que se escribió para ordenar la baraja
aleatoriamente. Solo que esta vez, en lugar de aplicar yield return a cada elemento, se
compararán los elementos coincidentes de cada secuencia. Después de que se haya
enumerado la secuencia completa, si cada elemento coincide, las secuencias son las
mismas:
C#
if (!firstIter.Current.Equals(secondIter.Current))
return false;
return true;
Esto muestra una segunda expresión LINQ: los métodos de terminal. Adoptan una
secuencia como entrada (o, en este caso, dos secuencias) y devuelven un único valor
escalar. Cuando se utilizan métodos de terminal, siempre son el método final en una
cadena de métodos para una consulta LINQ, de ahí el nombre "terminal".
Puede ver esto en acción cuando lo usa para determinar cuándo la baraja vuelve a tener
su orden original. Coloque el código de orden aleatorio dentro de un bucle y deténgalo
cuando la secuencia vuelva a su orden original, mediante la aplicación del método
SequenceEquals() . Puede observar que siempre se tratará del método final de cualquier
C#
// Program.cs
var times = 0;
// We can re-use the shuffle variable from earlier, or you can make a
new one
shuffle = startingDeck;
do
shuffle = shuffle.Take(26).InterleaveSequenceWith(shuffle.Skip(26));
Console.WriteLine(card);
Console.WriteLine();
times++;
} while (!startingDeck.SequenceEquals(shuffle));
Console.WriteLine(times);
Ejecute el código que tenga hasta el momento y tome nota de cómo la baraja se
reorganiza en cada orden aleatorio. Después de ocho órdenes aleatorios (iteraciones del
bucle do-while), la baraja vuelve a la configuración original en que se encontraba
cuando la creó a partir la consulta LINQ inicial.
Optimizaciones
El ejemplo creado hasta el momento se ejecuta en orden no aleatorio, donde las cartas
superiores e inferiores son las mismas en cada ejecución. Vamos a realizar un cambio:
utilizaremos una ejecución en orden aleatorio en su lugar, donde las 52 cartas cambian
de posición. Si se trata de un orden aleatorio, intercale la baraja de tal forma que la
primera carta de la mitad inferior sea la primera carta de la baraja. Esto significa que la
última carta de la mitad superior será la carta inferior. Se trata de un cambio simple en
una única línea de código. Actualice la consulta de orden aleatorio actual cambiando las
posiciones de Take y Skip. Se cambiará al orden de las mitades superior e inferior de la
baraja:
C#
shuffle = shuffle.Skip(26).InterleaveSequenceWith(shuffle.Take(26));
Vuelva a ejecutar el programa y verá que, para que la baraja se reordene, se necesitan
52 iteraciones. También empezará a observar algunas degradaciones graves de
rendimiento a medida que el programa continúa en ejecución.
Esto se debe a varias razones. Puede que se trate de una de las principales causas de
este descenso de rendimiento: un uso ineficaz de la evaluación diferida.
Recuerde que la baraja original se generó con una consulta LINQ. Cada orden aleatorio
se genera mediante la realización de tres consultas LINQ sobre la baraja anterior. Todas
se realizan de forma diferida. Eso también significa que se vuelven a llevar a cabo cada
vez que se solicita la secuencia. Cuando llegue a la iteración número 52, habrá
regenerado la baraja original demasiadas veces. Se va a escribir un registro para mostrar
este comportamiento. A continuación, podrá corregirlo.
C#
return sequence;
Verá un subrayado en zigzag rojo bajo File , lo que indica que no existe. No se
compilará, ya que el compilador no sabe qué es File . Para solucionar este problema,
asegúrese de agregar la siguiente línea de código bajo la primera línea de
Extensions.cs :
C#
using System.IO;
C#
// Program.cs
Console.WriteLine(c);
Console.WriteLine();
var times = 0;
do
// Out shuffle
/*
shuffle = shuffle.Take(26)
.LogQuery("Top Half")
.InterleaveSequenceWith(shuffle.Skip(26)
.LogQuery("Bottom Half"))
.LogQuery("Shuffle");
*/
// In shuffle
.InterleaveSequenceWith(shuffle.Take(26).LogQuery("Top
Half"))
.LogQuery("Shuffle");
Console.WriteLine(c);
}
times++;
Console.WriteLine(times);
} while (!startingDeck.SequenceEquals(shuffle));
Console.WriteLine(times);
Observe que no se genera un registro cada vez que accede a una consulta. El registro
solo se genera cuando crea la consulta original. El programa todavía tarda mucho
tiempo en ejecutarse, pero ahora puede ver por qué. Si se le agota la paciencia al
ejecutar el orden aleatorio interno con los registros activados, vuelva al orden aleatorio
externo. Aún puede ver los efectos de la evaluación diferida. En una ejecución, ejecuta
2592 consultas, incluida toda la generación de palos y valores.
Aquí puede mejorar el rendimiento del código para reducir el número de ejecuciones
que realiza. Una corrección sencilla que puede hacer es almacenar en caché los
resultados de la consulta LINQ original que construye la baraja de cartas. Actualmente,
ejecuta las consultas una y otra vez siempre que el bucle do-while pasa por una
iteración, lo que vuelve a construir la baraja de cartas y cambia continuamente el orden
aleatorio. Para almacenar en caché la baraja de cartas, puede aprovechar los métodos
LINQ ToArray y ToList; cuando los anexe a las consultas, realizarán las mismas acciones
que les ha indicado que hagan, pero ahora almacenarán los resultados en una matriz o
una lista, según el método al que elija llamar. Anexe el método ToArray de LINQ a las
dos consultas y ejecute de nuevo el programa:
C#
.LogQuery("Starting Deck")
.ToArray();
Console.WriteLine(c);
Console.WriteLine();
var times = 0;
do
/*
shuffle = shuffle.Take(26)
.LogQuery("Top Half")
.InterleaveSequenceWith(shuffle.Skip(26).LogQuery("Bottom
Half"))
.LogQuery("Shuffle")
.ToArray();
*/
shuffle = shuffle.Skip(26)
.LogQuery("Bottom Half")
.InterleaveSequenceWith(shuffle.Take(26).LogQuery("Top Half"))
.LogQuery("Shuffle")
.ToArray();
Console.WriteLine(c);
}
times++;
Console.WriteLine(times);
} while (!startingDeck.SequenceEquals(shuffle));
Console.WriteLine(times);
Este ejemplo está diseñado para resaltar los casos de uso en que la evaluación diferida
puede generar dificultades de rendimiento. Si bien es importante ver dónde la
evaluación diferida puede afectar al rendimiento del código, es igualmente importante
entender que no todas las consultas deben ejecutarse de manera diligente. El resultado
de rendimiento en el que incurre sin usar ToArray se debe a que cada nueva disposición
de la baraja de cartas se crea a partir de la disposición anterior. La evaluación diferida
supone que cada nueva configuración de la baraja se realiza a partir de la baraja
original, incluso con la ejecución del código que crea el elemento startingDeck . Esto
conlleva una gran cantidad de trabajo adicional.
En la práctica, algunos algoritmos se ejecutan bien con la evaluación diligente y otros,
con la evaluación diferida. Para el uso diario, la evaluación diferida suele ser una mejor
opción cuando el origen de datos es un proceso independiente, como un motor de
base de datos. Para las bases de datos, la evaluación diferida permite realizar consultas
más complejas que ejecuten un solo recorrido de ida y vuelta al procesamiento de la
base de datos y vuelvan al resto del código. LINQ es flexible tanto si decide usar la
evaluación diligente como la diferida, así que calibre sus procesos y elija el tipo de
evaluación que le ofrece el mejor rendimiento.
Conclusión
En este proyecto ha tratado lo siguiente:
Aparte de LINQ, ha aprendido algo sobre una técnica que los magos utilizan para hacer
trucos de cartas. Los magos usan el orden aleatorio Faro porque les permite controlar
dónde está cada carta en la baraja. Ahora que lo conoce, no se lo estropee a los demás.
En este tutorial, se le introducirá a cómo agregar atributos al código, cómo crear y usar
sus propios atributos y cómo usar algunos atributos que se integran en .NET Core.
Requisitos previos
Deberá configurar la máquina para ejecutar .NET Core. Puede encontrar las
instrucciones de instalación en la página Descargas de .NET Core .
Puede ejecutar esta
aplicación en Windows, Ubuntu Linux, macOS o en un contenedor de Docker.
Deberá
instalar su editor de código favorito. En las siguientes descripciones se usa Visual Studio
Code , que es un editor multiplataforma de código abierto. Sin embargo, puede usar
las herramientas que le resulten más cómodas.
Crear la aplicación
Ahora que ha instalado todas las herramientas, cree una nueva aplicación de .NET Core.
Para usar el generador de línea de comandos, ejecute el siguiente comando en su shell
favorito:
Este comando creará archivos de proyecto esenciales de .NET Core. Debe ejecutar
dotnet restore para restaurar las dependencias necesarias para compilar este proyecto.
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 ejecutar el programa, use dotnet run . Deberá ver la salida "Hola a todos" a la
consola.
C#
[Obsolete]
Cuando se marca una clase como obsoleta, es una buena idea proporcionar alguna
información de por qué es obsoleta, o qué usar en su lugar. Para ello se pasa un
parámetro de cadena al atributo obsoleto.
C#
C#
C#
[MySpecial]
Aquí hay un problema que se debe vigilar. Como se mencionó anteriormente, solo
determinados tipos se puedan pasar como argumentos al usar atributos. Sin embargo,
al crear un tipo de atributo, el compilador de C# no le impedirá crear esos parámetros.
En el ejemplo siguiente, he creado un atributo con un constructor que se compila
correctamente.
C#
Sin embargo, no podrá usar este constructor con una sintaxis de atributo.
C#
Ensamblado
Clase
Constructor
Delegar
Enumeración
evento
Campo
GenericParameter
Interfaz
Método
Módulo
Parámetro
Propiedad.
ReturnValue
Estructura
Cuando se crea una clase de atributo, de forma predeterminada C# podrá usar ese
atributo en cualquiera de los destinos de atributo posibles. Si desea restringir el atributo
a determinados destinos, puede hacerlo mediante la clase de atributo
AttributeUsageAttribute . ¡Sí, un atributo en un atributo!
C#
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class MyAttributeForClassAndStructOnly : Attribute
Si intenta colocar el atributo anterior en algo que no sea una clase o un struct, obtendrá
un error del compilador como Attribute 'MyAttributeForClassAndStructOnly' is not
valid on this declaration type. It is only valid on 'class, struct' declarations .
C#
// [MyAttributeForClassAndStructOnly]
public Foo()
{ }
Para buscar y actuar sobre los atributos, se requiere en general reflexión. En este tutorial
no se tratará la reflexión de forma detallada, pero la idea básica es que la reflexión le
permite escribir código en C# que examina otro código.
Por ejemplo, puede usar reflexión para obtener información sobre una clase (agregue
using System.Reflection; al principio del código):
C#
La salida será algo como esto: The assembly qualified name of MyClass is
ConsoleApplication.MyClass, attributes, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null .
Una vez que tenga un objeto TypeInfo (o un elemento MemberInfo , FieldInfo , etc.),
puede usar el método GetCustomAttributes . Este devolverá una colección de objetos
Attribute .
También puede usar GetCustomAttribute y especificar un tipo de atributo.
C#
vez. Al llamar a GetCustomAttributes dos veces en una fila se devuelven dos instancias
diferentes de ObsoleteAttribute .
Estos son algunos atributos importantes integrados en las bibliotecas de clases base de
.NET Core:
Este atributo se puede aplicar a métodos (o clases de atributos). Debe pasar una
cadena al constructor.
Si esa cadena no coincide con una directiva #define , el
compilador de C# quitará las llamadas a ese método (pero no el método
propiamente dicho). Normalmente se usa con fines de depuración (diagnósticos).
C#
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs(propertyName));
set
if (value != _name)
_name = value;
En el código anterior, no necesita tener una cadena "Name" de literal. Esto puede ayudar
a impedir errores relacionados con los tipos y también agiliza los procesos de
refactorización o cambio de nombre.
Resumen
Los atributos traen la eficacia declarativa a C#, pero son una forma de metadatos de
código y no actúan por sí mismos.
Tipos de referencia que aceptan valores
NULL
Artículo • 20/02/2023 • Tiempo de lectura: 20 minutos
En un contexto "oblivious" que acepta valores NULL, todos los tipos de referencia
aceptaban valores null. Los tipos de referencia que aceptan valores NULL hacen
referencia a un grupo de características habilitadas en un contexto que admite un valor
NULL que minimizan las probabilidades de que el código haga que el tiempo de
ejecución genere System.NullReferenceException. Los tipos de referencia que aceptan
valores NULL incluyen tres características que ayudan a evitar estas excepciones, incluida
la capacidad de marcar explícitamente un tipo de referencia como que acepta valores
NULL:
El análisis de flujo estático mejorado que determina si una variable puede estar
null antes de desreferenciarla.
Los atributos que anotan las API para que el análisis de flujo determine el null-
state.
Las anotaciones de variables que los desarrolladores usan para declarar
explícitamente el null-state previsto para una variable.
En el resto de este artículo se describe cómo funcionan esas tres áreas de características
para generar advertencias cuando el código puede estar desreferenciando un valor
null . Desreferenciar una variable significa acceder a uno de sus miembros mediante el
operador . (punto), como se muestra en el ejemplo siguiente:
C#
Al desreferenciar una variable cuyo valor es null , el entorno de ejecución produce una
excepción System.NullReferenceException.
También puede explorar estos conceptos en nuestro módulo de aprendizaje sobre
Seguridad sobre la aceptación de valores NULL en C#.
C#
// warning!
Console.WriteLine(originalMessage.Length);
C#
processNode(current);
El análisis del estado NULL no realiza un seguimiento de los métodos llamados. Como
resultado, los campos inicializados en un método auxiliar común llamado por
constructores generarán una advertencia con la plantilla siguiente:
La propiedad "name" que no acepta valores NULL debe contener un valor distinto
de NULL al salir del constructor.
C#
using System.Diagnostics.CodeAnalysis;
FirstName = firstName;
LastName = lastName;
: base(firstName, lastName)
SetMajor(major);
base(firstName, lastName)
SetMajor();
public Student()
SetMajor();
[MemberNotNull(nameof(Major))]
7 Nota
El análisis de estado que acepta valores NULL y las advertencias que genera el
compilador ayudan a evitar errores de programa mediante la anulación de la referencia
a null . En el artículo sobre cómo resolver advertencias que aceptan valores NULL se
proporcionan técnicas para corregir las advertencias que probablemente verá en el
código.
C#
if (!string.IsNullOrWhiteSpace(message))
Console.WriteLine($"{DateTime.Now}: {message}");
}
C#
Se usan anotaciones que pueden declarar si una variable es un tipo de referencia que
acepta valores NULL o un tipo de referencia que no acepta valores NULL. Estas
anotaciones hacen instrucciones importantes sobre el estado null-state de las variables:
Cualquier variable de referencia que no se suponga que es null tiene un estado null-
state de not-null. Cualquier variable de referencia que pueda ser null inicialmente tiene
el estado null-state de maybe-null.
Un tipo de referencia que acepta valores NULL se anota con la misma sintaxis que los
tipos de valor que aceptan valores NULL: se agrega ? junto al tipo de la variable. Por
ejemplo, la siguiente declaración de variable representa una variable de cadena que
acepta valores NULL, name :
C#
string? name;
En algunas ocaciones, debe invalidar una advertencia si sabe que una variable no es
NULL pero el compilador determina que su null-state es maybe-null. Use el operador
null-forgiving ! después de un nombre de variable para forzar que null-state sea not-
null. Por ejemplo, si sabe que la variable name no es null , pero el compilador genera
una advertencia, puede escribir el código siguiente para invalidar el análisis del
compilador:
C#
name!.Length;
Los tipos de referencia que aceptan valores NULL y los tipos de valor que aceptan
valores NULL proporcionan un concepto semántico similar: una variable puede
representar un valor u objeto, o esa variable puede ser null . Sin embargo, los tipos de
referencia que aceptan valores NULL y los tipos de valor que aceptan valores NULL se
implementan de forma diferente: los tipos de valor que aceptan valores NULL se
implementan mediante System.Nullable<T> y los tipos de referencia que aceptan
valores NULL se implementan mediante atributos leídos por el compilador. Por ejemplo,
string? y string se representan mediante el mismo tipo: System.String. Sin embargo,
int? y int se representan mediante System.Nullable<System.Int32> y System.Int32,
respectivamente.
Los tipos de referencia no nulas son una característica de tiempo de compilación. Esto
significa que es posible que los autores de llamadas ignoren las advertencias, utilizando
intencionadamente null como argumento de un método que espera una referencia no
nula. Los autores de bibliotecas deben incluir comprobaciones en tiempo de ejecución
con valores de argumento NULL. ArgumentNullException.ThrowIfNull es la opción
preferida para comparar un parámetro con NULL en tiempo de ejecución.
) Importante
Genéricos
Los genéricos requieren reglas detalladas para controlar T? para cualquier parámetro
de tipo T . Las reglas se detallan necesariamente debido al historial y a la
implementación diferente para un tipo de valor que acepta valores NULL y un tipo de
referencia que acepta valores NULL. Los tipos de valor que aceptan valores NULL se
implementan mediante la estructura System.Nullable<T>. Los tipos de referencia que
aceptan valores NULL se implementan como anotaciones de tipo que proporcionan
reglas semánticas al compilador.
La restricción class significa que T debe ser un tipo de referencia que no acepta
valores NULL (por ejemplo, string ). El compilador genera una advertencia si se
usa un tipo de referencia que acepta valores NULL, como string? para T .
La restricción class? significa que T debe ser un tipo de referencia, ya sea un tipo
de referencia que no acepta valores NULL ( string ) o un tipo de referencia que
acepta valores NULL (por ejemplo, string? ). Cuando el parámetro de tipo es un
tipo de referencia que acepta valores NULL, como string? , una expresión de T?
hace referencia a ese mismo tipo de referencia que acepta valores NULL, como
string? .
La restricción notnull significa que T debe ser un tipo de referencia que no
acepta valores NULL o un tipo de valor que no acepta valores NULL. Si usa un tipo
de referencia que acepta valores NULL o un tipo de valor que acepta valores NULL
para el parámetro de tipo, el compilador genera una advertencia. Además, cuando
T es un tipo de valor, el valor devuelto es ese tipo de valor, no el tipo de valor que
acepta valores NULL correspondiente.
Todas las variables de referencia con tipo explícito se interpretan como tipos de
referencia que no aceptan valores NULL.
El significado de la restricción class en genéricos cambió para significar un tipo
de referencia que no acepta valores NULL.
Se generan nuevas advertencias debido a estas nuevas reglas.
Debe elegir expresamente usar estas características en los proyectos existentes. Esto
proporciona una ruta de migración y conserva la compatibilidad con versiones
anteriores. Los contextos que aceptan valores NULL permiten un control preciso sobre
cómo interpreta el compilador las variables de tipos de referencia. El contexto de
anotación que acepta valores NULL determina el comportamiento del compilador. Hay
cuatro valores para el contexto de anotación que acepta valores NULL:
Elija disable para los proyectos heredados que no quiere actualizar en función de
diagnósticos o nuevas características.
Elija warnings para determinar dónde el código puede producir
System.NullReferenceException. Puede solucionar esas advertencias antes de
modificar el código para habilitar tipos de referencia que no aceptan valores NULL.
Elija annotations para expresar la intención de diseño antes de habilitar las
advertencias.
Elija enable para nuevos proyectos y proyectos activos en los que quiera
protegerse de excepciones de referencia nula.
Ejemplo:
XML
<Nullable>enable</Nullable>
También puede usar directivas para establecer los mismos contextos en cualquier lugar
del código fuente: Son muy útiles cuando se migra un código base grande.
disable.
#nullable enable warnings : establece el contexto de advertencia nula en enable.
Esas nueve combinaciones proporcionan un control preciso sobre los diagnósticos que
el compilador emite para el código. Puede habilitar más características en cualquier área
que esté actualizando sin ver advertencias adicionales que aún no está listo para
abordar.
) Importante
El contexto global que admite un valor NULL no se aplica a los archivos de código
generado. En cualquier estrategia, el contexto que admite un valor NULL está
deshabilitado para cualquier archivo de código fuente marcado como generado.
Esto significa que las API de los archivos generados no se anotan. Hay cuatro
maneras de marcar un archivo como generado:
Estas opciones proporcionan dos estrategias distintas para actualizar un código base
existente para usar tipos de referencia que aceptan valores NULL.
Problemas conocidos
Las matrices y estructuras que contienen tipos de referencia son dificultades conocidas
en las referencias que aceptan valores NULL y el análisis estático que determina la
seguridad de los valores NULL. En ambas situaciones, se puede inicializar una referencia
que no acepta valores NULL en null sin generar advertencias.
Estructuras
Una estructura que contiene tipos de referencia que no aceptan valores NULL permite
asignarle default sin ninguna advertencia. Considere el ejemplo siguiente:
C#
using System;
#nullable enable
C#
#nullable enable
string s = default(Foo<string>).Bar;
Matrices
Las matrices también son un problema conocido en los tipos de referencia que aceptan
valores NULL. Considere el ejemplo siguiente, que no genera ninguna advertencia:
C#
using System;
#nullable enable
string s = values[0];
Console.WriteLine(s.ToUpper());
Vea también
Tipos de referencia que aceptan valores NULL: propuesta
Borrador de especificación de tipos de referencia que aceptan valores NULL
Anotaciones de parámetros de tipo sin restricciones
Tutorial de introducción a las referencias que no aceptan valores NULL
Nullable (opción del compilador de C#)
Actualización de un código base con
tipos de referencia que admiten un
valor NULL para mejorar las
advertencias sobre diagnósticos nulos
Artículo • 15/02/2023 • Tiempo de lectura: 8 minutos
Los tipos de referencia que admiten un valor NULL permiten declarar si a las variables
de un tipo de referencia se les debe asignar o no un valor null . El análisis estático y las
advertencias del compilador cuando el código podría desreferenciar null son la ventaja
más importante de esta característica. Una vez habilitado, el compilador genera
advertencias que ayudan a evitar que se genere una excepción
System.NullReferenceException cuando se ejecuta el código.
Planeación de la migración
Independientemente de cómo actualice su código base, el objetivo es que las
advertencias y anotaciones que admiten un valor NULL estén habilitadas en su proyecto.
Una vez que alcance ese objetivo, tendrá el valor <nullable>Enable</nullable> en su
proyecto. No necesitará ninguna de las directivas del preprocesador para ajustar la
configuración en otro lugar.
Al habilitar una opción que admite un valor NULL como predeterminada, se genera más
trabajo previo para agregar las directivas del preprocesador a cada archivo. La ventaja es
que todos los archivos de código nuevos que se agreguen al proyecto estarán
habilitados para aceptar valores NULL. Cualquier trabajo nuevo admitirá valores NULL;
solo se debe actualizar el código existente. Si se deshabilita la opción que admite un
valor NULL, este proceso funcionará mejor si la biblioteca es estable y el objetivo
principal del desarrollo es adoptar tipos de referencia que admiten un valor NULL. Los
tipos de referencia que aceptan valores NULL se habilitan al anotar las API. Cuando haya
terminado, habilite los tipos de referencia que aceptan valores NULL para todo el
proyecto. Al crear un archivo nuevo, debe agregar las directivas del preprocesador y
hacer que sea compatible con los valores NULL. Si alguno de los desarrolladores de su
equipo se olvida de hacerlo, ese nuevo código se sumará al trabajo pendiente para
hacer que todo el código admita valores NULL.
) Importante
El contexto global que admite un valor NULL no se aplica a los archivos de código
generado. En cualquier estrategia, el contexto que admite un valor NULL está
deshabilitado para cualquier archivo de código fuente marcado como generado.
Esto significa que las API de los archivos generados no se anotan. Hay cuatro
maneras de marcar un archivo como generado:
oblivious: todos los tipos de referencia son de tipo oblivious que admiten un valor
NULL cuando el contexto de anotación de deshabilita.
nonnullable: tipo de referencia no anotado; C es nonnullable cuando el contexto
de anotación se habilite.
nullable: tipo de referencia anotado; C? es de tipo nullable, pero puede que se
genere una advertencia cuando el contexto de anotación se deshabilite. Las
variables declaradas con var son de tipo nullable cuando el contexto de anotación
se habilita.
Cada variable tiene un estado predeterminado que admite un valor NULL que depende
de su nulabilidad:
Las variables que admiten un valor NULL tienen un estado predeterminado null-
state de maybe-null.
Las variables que no admiten un valor NULL tienen un estado predeterminado
null-state de not-null.
Las variables de tipo "oblivious" que admiten un valor NULL tienen un estado
predeterminado null-state de not-null.
Antes de habilitar los tipos de referencia que aceptan valores NULL, todas las
declaraciones del código base son de tipo nullable oblivious. Esto es importante porque
significa que todos los tipos de referencia tienen in estado predeterminado null-state de
not-null.
Resolución de advertencias
Si su proyecto usa Entity Framework Core, debe leer sus guías sobre cómo trabajar con
tipos de referencia que admiten un valor NULL.
Al iniciar la migración, debe empezar habilitando solo las advertencias. Todas las
declaraciones siguen siendo de tipo nullable oblivious, pero se le mostrarán advertencias
cuando desreferencie un valor después de que su estado null-state cambie a maybe-
null. A medida que se resuelvan estas advertencias, realizará comprobaciones de NULL
en más ubicaciones, y su código base será más resistente. Para obtener información
sobre técnicas específicas para diferentes situaciones, vea el artículo sobre Técnicas para
resolver advertencias que admiten un valor NULL.
Pasos siguientes
Una vez que haya resuelto todas las advertencias después de habilitar las anotaciones,
podrá establecer el contexto predeterminado para su proyecto en enabled. Si ha
agregado alguna pragma a su código para la anotación que admite un valor NULL o el
contexto de advertencia, la podrá eliminar. Con el tiempo, es posible que se le muestren
nuevas advertencias. Puede escribir código que introduzca advertencias. Una
dependencia de biblioteca se puede actualizar para los tipos de referencia que aceptan
valores NULL. Esas actualizaciones cambiarán los tipos de esa biblioteca de nullable
oblivious a nonnullable o nullable.
Solucionará casi todas las advertencias usando una de estas cuatro técnicas:
C#
class Container
Console.WriteLine(message.Length); // CS8602
Para quitar estas advertencias, debe agregar código para cambiar el null-state de esa
variable a not-null (no es NULL) antes de desreferenciarla. La advertencia del
inicializador de colección puede ser más difícil de detectar. El compilador detecta que la
colección maybe-null cuando el inicializador le agrega elementos.
En muchos casos, puede corregir estas advertencias comprobando que una variable no
es NULL antes de desreferenciarla. Precisamente, el ejemplo anterior se podría reescribir
como:
C#
Console.WriteLine(message.Length);
En algunos casos, estas advertencias pueden ser falsos positivos. Es posible que tenga
un método de utilidad privada que haga pruebas sobre los valores NULL. El compilador
no sabe que el método proporciona una comprobación de valores NULL. Fíjese en este
ejemplo, que usa un método de utilidad privada, IsNotNull :
C#
if (IsNotNull(message))
Console.WriteLine(message.Length);
C#
Console.WriteLine(message!.Length);
El operador que permite valores NULL hace que la expresión sea not-null incluso si era
maybe-null antes de aplicar ! . En este ejemplo, una solución mejor es agregar un
atributo a la firma de IsNotNull :
C#
private static bool IsNotNull([NotNullWhen(true)] object? obj) => obj !=
null;
Sugerencia
Hay un amplio conjunto de atributos que puede usar para describir cómo afectan
los métodos y propiedades al null-state. Puede obtener información sobre ellos en
el artículo de referencia del lenguaje sobre Atributos de análisis estático que
aceptan valores NULL.
C#
Puede realizar una de estas tres acciones para solucionar estas advertencias. Una opción
es agregar la anotación ? para convertir la variable en un tipo de referencia que acepta
valores NULL. Ese cambio puede provocar otras advertencias. Cambiar una variable de
una referencia que no acepta valores NULL a una que sí los acepta cambia su null-state
predeterminado de not-null a maybe-null. El análisis estático del compilador puede
encontrar instancias en las que se desreferencia una variable que es maybe-null.
C#
C#
La corrección de una advertencia para asignar una expresión maybe-null a una variable
not-null implica una de estas cuatro técnicas:
Cambiar el lado izquierdo de la asignación a un tipo que acepte valores NULL. Esta
acción puede introducir nuevas advertencias al desreferenciar esa variable.
Proporcionar una comprobación de valores NULL antes de la asignación.
Anotar la API que genera el lado derecho de la asignación.
Agregar el operador que permite valores NULL al lado derecho de la asignación.
CS8618 - La variable que no acepta valores NULL debe contener un valor distinto de
NULL al salir del constructor. Considere la posibilidad de declararlo como que acepta
valores NULL.
CS8762 - El parámetro debe tener un valor distinto de NULL al salir.
C#
C#
FirstName = first;
LastName = last;
Si necesita crear un objeto Person antes de establecer el nombre, puede inicializar las
propiedades mediante un valor no NULL predeterminado:
C#
Otra alternativa puede ser cambiar esos miembros a tipos de referencia que aceptan
valores NULL. La clase Person se puede definir de la siguiente manera si null debe
permitirse para el nombre:
C#
El código existente puede necesitar otros cambios para informar al compilador sobre la
semántica NULL de esos miembros. Es posible que haya creado varios constructores y
que la clase tenga un método auxiliar privado que inicialice uno o varios miembros.
Puede mover el código de inicialización a un único constructor y asegurarse de que
todos los constructores llaman al que tiene el código de inicialización común. O puede
usar los atributos System.Diagnostics.CodeAnalysis.MemberNotNullAttribute y
System.Diagnostics.CodeAnalysis.MemberNotNullWhenAttribute. Estos atributos
informan al compilador de que un miembro es not-null después de que se llame al
método. En el código siguiente se muestra un ejemplo de cada caso. La clase Person
usa un constructor común al que llaman todos los demás constructores. La clase
Student tiene un método auxiliar anotado con el atributo
System.Diagnostics.CodeAnalysis.MemberNotNullAttribute:
C#
using System.Diagnostics.CodeAnalysis;
FirstName = firstName;
LastName = lastName;
: base(firstName, lastName)
SetMajor(major);
base(firstName, lastName)
SetMajor();
public Student()
SetMajor();
[MemberNotNull(nameof(Major))]
Por último, puede usar el operador que permite valores NULL para indicar que un
miembro se inicializa en otro código. Por dar otro ejemplo, fíjese en las siguientes clases
que representan un modelo de Entity Framework Core:
C#
: base(options)
{
C#
public class B
public class D : B
Otras situaciones pueden generar estas advertencias. Es posible que tenga un error de
coincidencia entre una declaración del método de interfaz y la implementación de ese
método. O bien, un tipo de delegado y la expresión de ese delegado pueden diferir. Un
parámetro de tipo y el argumento de tipo pueden diferir en su nulabilidad.
CS8607 - Es posible que no se use un valor NULL posible para un tipo marcado con
[NotNull] o [DisallowNull]
CS8763 - Un método marcado [DoesNotReturn] no debe devolverse.
CS8770 - El método carece de [DoesNotReturn] anotación para que coincida con el
miembro implementado o invalidado.
CS8774 - El miembro debe tener un valor distinto de NULL al salir.
CS8776 - El miembro no se puede usar en este atributo.
CS8775 - El miembro debe tener un valor distinto de NULL al salir.
CS87777 - El parámetro debe tener un valor distinto de NULL al salir.
CS8824 - El parámetro debe tener un valor distinto de NULL al salir porque el
parámetro no es NULL.
CS8825 - El valor devuelto debe ser distinto de NULL porque el parámetro no es
NULL.
C#
message = null;
return true;
Para solucionar estas advertencias, actualice el código para que coincida con las
expectativas de los atributos que ha aplicado. Puede cambiar los atributos o el
algoritmo.
C#
status switch
"Red" => 0,
"Yellow" => 5,
{ } => -1
};
por { } el _ patrón (descartar). El patrón de descarte coincide con null, así como con
cualquier otro valor.
Métodos de C#
Artículo • 14/02/2023 • Tiempo de lectura: 23 minutos
7 Nota
Firmas de método
Los métodos se declaran en un elemento class , record o struct al especificar lo
siguiente:
) Importante
En el siguiente ejemplo se define una clase denominada Motorcycle que contiene cinco
métodos:
C#
using System;
Tenga en cuenta que la clase Motorcycle incluye un método sobrecargado, Drive . Dos
métodos tienen el mismo nombre, pero se deben diferenciar en sus tipos de
parámetros.
Invocación de método
Los métodos pueden ser de instancia o estáticos. Para invocar un método de instancia es
necesario crear una instancia de un objeto y llamar al método del objeto; el método de
una instancia actúa en dicha instancia y sus datos. Si quiere invocar un método estático,
haga referencia al nombre del tipo al que pertenece el método; los métodos estáticos
no actúan en datos de instancia. Al intentar llamar a un método estático mediante una
instancia de objeto se genera un error del compilador.
Llamar a un método es como acceder a un campo. Después del nombre de objeto (si
llama a un método de instancia) o el nombre de tipo (si llama a un método static ),
agregue un punto, el nombre del método y paréntesis. Los argumentos se enumeran
entre paréntesis y se separan mediante comas.
La definición del método especifica los nombres y tipos de todos los parámetros
necesarios. Cuando un autor de llamada invoca el método, proporciona valores
concretos denominados argumentos para cada parámetro. Los argumentos deben ser
compatibles con el tipo de parámetro, pero el nombre de argumento, si se usa alguno
en el código de llamada, no tiene que ser el mismo que el del parámetro con nombre
definido en el método. En el ejemplo siguiente, el método Square incluye un parámetro
único de tipo int denominado i. La primera llamada de método pasa al método Square
una variable de tipo int denominada num; la segunda, una constante numérica; y la
tercera, una expresión.
C#
int num = 4;
int input = i;
C#
return 108.4;
moto.StartEngine();
moto.AddGas(15);
moto.Drive(5, 20);
C#
using System;
return 108.4;
moto.StartEngine();
moto.AddGas(15);
C#
C#
using System;
p1.FirstName = "John";
p2.FirstName = "John";
// p1 = p2: False
Los tipos pueden invalidar miembros heredados usando la palabra clave override y
proporcionando una implementación para el método invalidado. La firma del método
debe ser igual a la del método invalidado. El ejemplo siguiente es similar al anterior,
salvo que invalida el método Equals(Object). (También invalida el método
GetHashCode(), ya que los dos métodos están diseñados para proporcionar resultados
coherentes).
C#
using System;
if (p2 == null)
return false;
else
return FirstName.Equals(p2.FirstName);
return FirstName.GetHashCode();
p1.FirstName = "John";
p2.FirstName = "John";
// p1 = p2: True
Pasar parámetros
Todos los tipos de C# son tipos de valor o tipos de referencia. Para obtener una lista de
tipos de valor integrados, vea Tipos. De forma predeterminada, los tipos de valor y los
tipos de referencia se pasan a un método por valor.
C#
using System;
ModifyValue(value);
i = 30;
return;
// In Main, value = 20
Cuando un objeto de un tipo de referencia se pasa a un método por valor, se pasa por
valor una referencia al objeto. Es decir, el método no recibe el objeto concreto, sino un
argumento que indica la ubicación del objeto. Si cambia un miembro del objeto
mediante esta referencia, el cambio se reflejará en el objeto cuando el control vuelva al
método de llamada. Pero el reemplazo del objeto pasado al método no tendrá ningún
efecto en el objeto original cuando el control vuelva al autor de la llamada.
C#
using System;
rt.value = 44;
ModifyObject(rt);
Console.WriteLine(rt.value);
obj.value = 33;
}
El ejemplo siguiente es idéntico al anterior, salvo que el valor se pasa por referencia al
método ModifyValue . Cuando se modifica el valor del parámetro en el método
ModifyValue , el cambio del valor se refleja cuando el control vuelve al autor de la
llamada.
C#
using System;
ModifyValue(ref value);
i = 30;
return;
// In Main, value = 20
Un patrón común que se usa en parámetros ref implica intercambiar los valores de
variables. Se pasan dos variables a un método por referencia y el método intercambia su
contenido. En el ejemplo siguiente se intercambian valores enteros.
C#
using System;
int i = 2, j = 3;
int temp = x;
x = y;
y = temp;
// i = 2 j = 3
// i = 3 j = 2
Matrices de parámetros
A veces, el requisito de especificar el número exacto de argumentos al método es
restrictivo. El uso de la palabra clave params para indicar que un parámetro es una
matriz de parámetros permite llamar al método con un número variable de argumentos.
El parámetro etiquetado con la palabra clave params debe ser un tipo de matriz y ser el
último parámetro en la lista de parámetros del método.
Un autor de llamada puede luego invocar el método de una de las tres maneras
siguientes:
Si se pasa una matriz del tipo adecuado que contenga el número de elementos
que se quiera.
Si se pasa una lista separada por comas de los argumentos individuales del tipo
adecuado para el método.
Pasando null .
Si no se proporciona un argumento a la matriz de parámetros.
C#
using System;
using System.Linq;
class ParamsExample
return string.Empty;
return string.Concat(
input.SelectMany(
El valor predeterminado del parámetro debe asignarse con uno de los siguientes tipos
de expresiones:
7 Nota
C#
using System;
Console.WriteLine(msg);
En el ejemplo siguiente se llama tres veces al método ExampleMethod . Las dos primeras
llamadas al método usan argumentos posicionales. La primera omite los dos
argumentos opcionales, mientras que la segunda omite el último argumento. La tercera
llamada de método proporciona un argumento posicional para el parámetro necesario,
pero usa un argumento con nombre para proporcionar un valor al parámetro
description mientras omite el argumento optionalInt .
C#
opt.ExampleMethod(10);
opt.ExampleMethod(10, 2);
// N/A: 10 + 0 = 10
// N/A: 10 + 2 = 12
Valores devueltos
Los métodos pueden devolver un valor al autor de llamada. Si el tipo de valor devuelto
(el tipo que aparece antes del nombre de método) no es void , el método puede
devolver el valor mediante la palabra clave return . Una instrucción con la palabra clave
return seguida de una variable, una constante o una expresión que coincide con el tipo
de valor devuelto devolverá este valor al autor de la llamada al método. Los métodos
con un tipo de valor devuelto no nulo son necesarios para usar la palabra clave return
para devolver un valor. La palabra clave return también detiene la ejecución del
método.
Si el tipo de valor devuelto es void , una instrucción return sin un valor también es útil
para detener la ejecución del método. Sin la palabra clave return , el método dejará de
ejecutarse cuando alcance el final del bloque de código.
Por ejemplo, estos dos métodos utilizan la palabra clave return para devolver enteros:
C#
class SimpleMath
C#
result = obj.SquareANumber(result);
// The result is 9.
Console.WriteLine(result);
C#
// The result is 9.
Console.WriteLine(result);
Usar una variable local, en este caso, result , para almacenar un valor es opcional. La
legibilidad del código puede ser útil, o puede ser necesaria si debe almacenar el valor
original del argumento para todo el ámbito del método.
A veces, quiere que el método devuelva más que un solo valor. Puede hacer esto
fácilmente utilizando tipos de tupla y literales de tupla. El tipo de tupla define los tipos
de datos de los elementos de la tupla. Los literales de tupla proporcionan los valores
reales de la tupla devuelta. En el ejemplo siguiente, (string, string, string, int)
define el tipo de tupla que devuelve el método GetPersonalInfo . La expresión
(per.FirstName, per.MiddleName, per.LastName, per.Age) es el literal de tupla; el
método devuelve el nombre, los apellidos y la edad de un objeto PersonInfo .
C#
Luego, el autor de la llamada puede usar la tupla devuelta con código como el
siguiente:
C#
C#
public (string FName, string MName, string LName, int Age)
GetPersonalInfo(string id)
C#
C#
using System;
int[] values = { 2, 4, 6, 8 };
DoubleValues(values);
arr[ctr] = arr[ctr] * 2;
// 4 8 12 16
Métodos de extensión
Normalmente, hay dos maneras de agregar un método a un tipo existente:
Métodos asincrónicos
Mediante la característica asincrónica, puede invocar métodos asincrónicos sin usar
definiciones de llamada explícitas ni dividir manualmente el código en varios métodos o
expresiones lambda.
7 Nota
C#
class Program
Console.WriteLine($"Result: {result}");
await Task.Delay(100);
return 5;
// Example output:
// Result: 5
Un método asincrónico no puede declarar ningún parámetro in, ref o out, pero puede
llamar a los métodos que tienen estos parámetros.
Para obtener más información sobre los métodos asincrónicos, consulte los artículos
Programación asincrónica con async y await y Tipos de valor devueltos asincrónicos.
C#
public Point Move(int dx, int dy) => new Point(x + dx, y + dy);
Iterators
Un iterador realiza una iteración personalizada en una colección, como una lista o
matriz. Un iterador utiliza la instrucción yield return para devolver cada elemento de uno
en uno. Cuando se llega a una instrucción yield return , se recuerda la ubicación actual
para que el autor de la llamada pueda solicitar el siguiente elemento en la secuencia.
Consulte también
Modificadores de acceso
Clases estáticas y sus miembros
Herencia
Clases y miembros de clase abstractos y sellados
params
out
ref
in
Pasar parámetros
Propiedades
Artículo • 15/02/2023 • Tiempo de lectura: 11 minutos
Las propiedades son ciudadanos de primera clase en C#. El lenguaje define la sintaxis
que permite a los desarrolladores escribir código que exprese con precisión su intención
de diseño.
Las propiedades se comportan como campos cuando se obtiene acceso a ellas. Pero, a
diferencia de los campos, las propiedades se implementan con descriptores de acceso
que definen las instrucciones que se ejecutan cuando se tiene acceso a una propiedad o
se asigna.
C#
C#
C#
La inicialización específica es más útil en las propiedades de solo lectura, como verá
posteriormente en este artículo.
C#
C#
Estos son los conceptos básicos de la sintaxis. Hay muchas variantes distintas que
admiten diversos lenguajes de diseño diferentes. Vamos a explorarlas y a aprender las
opciones de sintaxis de cada una.
Validación
Los ejemplos anteriores mostraron uno de los casos más simples de definición de
propiedad: una propiedad de lectura y escritura sin validación. Al escribir el código que
quiere en los descriptores de acceso get y set , puede crear muchos escenarios
diferentes.
Puede escribir código en el descriptor de acceso set para asegurarse de que los valores
representados por una propiedad siempre son válidos. Por ejemplo, suponga que una
regla para la clase Person es que el nombre no puede estar en blanco ni tener espacios
en blanco. Se escribiría de esta forma:
C#
set
if (string.IsNullOrWhiteSpace(value))
_firstName = value;
El ejemplo anterior se puede simplificar usando una expresión throw como parte de la
validación del establecedor de propiedad:
C#
C#
hero.FirstName = "";
Se puede extender esta misma sintaxis para todo lo que se necesite en el escenario. Se
pueden comprobar las relaciones entre las diferentes propiedades o validar con
respecto a cualquier condición externa. Todas las instrucciones de C# válidas son válidas
en un descriptor de acceso de propiedad.
Control de acceso
Hasta ahora, todas las definiciones de propiedad que se vieron son propiedades de
lectura y escritura con descriptores de acceso públicos. No es la única accesibilidad
válida para las propiedades. Se pueden crear propiedades de solo lectura, o
proporcionar accesibilidad diferente a los descriptores de acceso set y get. Suponga que
su clase Person solo debe habilitar el cambio del valor de la propiedad FirstName desde
otros métodos de esa clase. Podría asignar al descriptor de acceso set la accesibilidad
private en lugar de public :
C#
Ahora, se puede obtener acceso a la propiedad FirstName desde cualquier código, pero
solo puede asignarse desde otro código de la clase Person .
Solo lectura
También puede restringir las modificaciones de una propiedad, de manera que solo
pueda establecerse en un constructor. Puede modificar la clase Person de la manera
siguiente:
C#
Solo inicialización
En el ejemplo anterior se requieren llamadas para usar el constructor que incluye el
parámetro FirstName . Los autores de llamadas no pueden usar inicializadores de
objetos para asignar un valor a la propiedad. Para admitir inicializadores, puede
convertir el descriptor de acceso set en un descriptor de acceso init , como se muestra
en el código siguiente:
C#
public Person() { }
esa propiedad:
C#
public Person() { }
[SetsRequiredMembers]
El código anterior realiza dos adiciones a la clase Person . En primer lugar, la declaración
de la propiedad FirstName incluye el modificador required . Esto significa que cualquier
código que cree un nuevo Person debe establecer esta propiedad. En segundo lugar, el
constructor que toma un parámetro firstName tiene el atributo
System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute. Este atributo informa
al compilador de que este constructor establece todos los miembros de required .
) Importante
No confunda required con que no acepta valores NULL. Es válido establecer una
propiedad required en null o default . Si el tipo no acepta valores NULL, como
string en estos ejemplos, el compilador emite una advertencia.
C#
Propiedades calculadas
Una propiedad no tiene por qué devolver únicamente el valor de un campo de
miembro. Se pueden crear propiedades que devuelvan un valor calculado. Vamos a
ampliar el objeto Person para que devuelva el nombre completo, que se calcula
mediante la concatenación del nombre y el apellido:
C#
También se pueden usar un miembro con forma de expresión, que proporciona una
manera más concisa de crear la propiedad FullName calculada:
C#
Los miembros con forma de expresión usan la sintaxis de expresión lambda para definir
métodos que contienen una única expresión. En este caso, esa expresión devuelve el
nombre completo para el objeto person.
C#
get
if (_fullName is null)
return _fullName;
C#
set
_firstName = value;
_fullName = null;
set
_lastName = value;
_fullName = null;
get
if (_fullName is null)
return _fullName;
Esta versión final da como resultado la propiedad FullName solo cuando sea necesario.
Si la versión calculada previamente es válida, es la que se usa. Si otro cambio de estado
invalida la versión calculada previamente, se vuelve a calcular. No es necesario que los
desarrolladores que usan esta clase conozcan los detalles de la implementación.
Ninguno de estos cambios internos afectan al uso del objeto Person. Es el motivo
principal para usar propiedades para exponer los miembros de datos de un objeto.
C#
[field:NonSerialized]
Esta técnica funciona con cualquier atributo que se asocie al campo de respaldo en la
propiedad implementada automáticamente.
Implementar INotifyPropertyChanged
Un último escenario donde se necesita escribir código en un descriptor de acceso de
propiedad es para admitir la interfaz INotifyPropertyChanged que se usa para notificar a
los clientes de enlace de datos el cambio de un valor. Cuando se cambia el valor de una
propiedad, el objeto genera el evento INotifyPropertyChanged.PropertyChanged para
indicar el cambio. A su vez, las bibliotecas de enlace de datos actualizan los elementos
de visualización en función de ese cambio. El código siguiente muestra cómo se
implementaría INotifyPropertyChanged para la propiedad FirstName de esta clase
person.
C#
set
if (string.IsNullOrWhiteSpace(value))
if (value != _firstName)
_firstName = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(FirstName)));
Resumen
Las propiedades son una forma de campos inteligentes en una clase o un objeto. Desde
fuera del objeto, parecen campos en el objeto. Pero las propiedades pueden
implementarse mediante la paleta completa de funcionalidad de C#. Se puede
proporcionar validación, tipos diferentes de accesibilidad, evaluación diferida o los
requisitos que se necesiten para cada escenario.
Indizadores
Artículo • 22/09/2022 • Tiempo de lectura: 9 minutos
Los indizadores son similares a las propiedades. Muchas veces, los indizadores se basan
en las mismas características del lenguaje que las propiedades. Los indizadores permiten
las propiedades indizadas: propiedades a las que se hace referencia mediante uno o
más argumentos. Estos argumentos proporcionan un índice en alguna colección de
valores.
C#
someObject["AnotherKey"] = item;
Los indizadores se declaran con la palabra clave this como nombre de la propiedad y
declarando los argumentos entre corchetes. Esta declaración coincidiría con el uso que
se muestra en el párrafo anterior:
C#
En este ejemplo inicial puede ver la relación existente entre la sintaxis de las
propiedades y los indizadores. Esta analogía lleva a cabo la mayoría de las reglas de
sintaxis de los indizadores. Los indizadores pueden tener cualquier modificador de
acceso válido (público, interno protegido, protegido, interno, privado o privado
protegido). Pueden ser sellados, virtuales o abstractos. Al igual que con las propiedades,
puede especificar distintos modificadores de acceso para los descriptores de acceso get
y set en un indizador.
También puede especificar indizadores de solo lectura (omitiendo
el descriptor de acceso set) o indizadores de solo escritura (omitiendo el descriptor de
acceso get).
Puede aplicar a los indizadores casi todo lo que aprenda al trabajar con propiedades. La
única excepción a esta regla son las propiedades implementadas automáticamente. El
compilador no siempre puede generar el almacenamiento correcto para un indizador.
Escenarios
Tendría que definir indizadores en el tipo si su API modela alguna colección en la que se
definen los argumentos de esa colección. Los indizadores pueden (o no) asignarse
directamente a los tipos de colección que forman parte del marco de trabajo principal
de .NET. El tipo puede tener otras responsabilidades, además de tener que modelar una
colección.
Los indizadores le permiten proporcionar la API que coincida con la
abstracción de su tipo sin tener que exponer la información interna de cómo se
almacenan o se calculan los valores de dicha abstracción.
Veamos algunos de los escenarios habituales en los que se usan los indizadores. Puede
obtener acceso a la carpeta de ejemplo para indexadores . Para obtener instrucciones
de descarga, vea Ejemplos y tutoriales.
Matrices y vectores
Uno de los escenarios más comunes para crear indizadores es cuando el tipo modela
una matriz o un vector. Puede crear un indizador para modelar una lista ordenada de
datos.
C#
public class DataSamples
this.startingIndex = startingIndex;
this.length = length;
lastAccess = DateTime.Now;
};
pageData.Add(m);
get
lastAccess = DateTime.Now;
set
dirty = true;
lastAccess = DateTime.Now;
this.totalSize = totalSize;
get
if (index < 0)
return page[index];
set
if (index < 0)
page[index] = value;
if (p.HasItem(index))
return p;
addPageToCache(newPage);
return newPage;
if (pagesInMemory.Count > 4)
.FirstOrDefault();
if (oldest != null)
pagesInMemory.Remove(oldest);
pagesInMemory.Add(p);
Puede seguir esta expresión de diseño para modelar cualquier tipo de colección cuando
haya motivos de peso para no cargar todo el conjunto de datos en una colección en
memoria. Observe que la clase Page es una clase anidada privada que no forma parte
de la interfaz pública. Estos datos se ocultan a los usuarios de esta clase.
Diccionarios
Otro escenario habitual es cuando necesita modelar un diccionario o una asignación. En
este escenario, el tipo almacena valores en función de la clave (normalmente claves de
texto). En este ejemplo se crea un diccionario que asigna argumentos de la línea de
comandos a expresiones lambda que administran estas opciones. En el ejemplo
siguiente se muestran dos clases: una clase ArgsActions , que asigna una opción de la
línea de comandos a un delegado Action ; y ArgsProcessor , que usa ArgsActions para
ejecutar cada Action cuando encuentra esa opción.
C#
this.actions = actions;
actions[arg]?.Invoke();
get
Action action;
argsActions[s] = a;
Asignaciones multidimensionales
Puede crear indizadores que usen varios argumentos. Además, estos argumentos no se
restringen para que sean del mismo tipo. Veamos dos ejemplos.
En el primer ejemplo se muestra una clase que genera valores para el conjunto
Mandelbrot. Para obtener más información sobre las matemáticas subyacentes en el
conjunto, lea este artículo .
El indizador usa dos valores double para definir un punto
en el plano X, Y.
El descriptor de acceso get calcula el número de iteraciones existente
hasta que se determina que un punto no está en el conjunto. Si se alcanza el número
máximo de iteraciones, el punto está en el conjunto y se devuelve el valor maxIterations
de la clase (Las imágenes generadas por un equipo popularizadas para el conjunto
Mandelbrot definen colores para el número de iteraciones que son necesarias para
determinar que un punto en concreto está fuera del conjunto).
C#
this.maxIterations = maxIterations;
get
var iterations = 0;
var x0 = x;
var y0 = y;
y = 2 * x * y + y0;
x = newX;
iterations++;
return iterations;
Vamos a examinar un último uso de los indizadores, en el que el indizador toma varios
argumentos de distintos tipos. Imagínese un programa que administra datos históricos
de temperaturas. Este indizador usa una ciudad y una fecha para establecer u obtener
las temperaturas máximas y mínimas de ese lugar:
C#
using DateMeasurements =
System.Collections.Generic.Dictionary<System.DateTime,
IndexersSamples.Common.Measurements>;
using CityDataMeasurements =
System.Collections.Generic.Dictionary<string,
System.Collections.Generic.Dictionary<System.DateTime,
IndexersSamples.Common.Measurements>>;
get
return measure;
set
storage.Add(city, cityData);
cityData[index] = value;
Este ejemplo crea un indizador que asigna los datos meteorológicos en dos argumentos
diferentes: una ciudad (representada por string ) y una fecha (representada por
DateTime ). El almacenamiento interno usa dos clases Dictionary para representar el
diccionario bidimensional. La API pública ya no representa el almacenamiento
subyacente. En su lugar, las características del lenguaje de los indizadores le permiten
crear una interfaz pública que representa la abstracción, aunque el almacenamiento
subyacente debe usar distintos tipos de colección básica.
Hay dos partes de este código que pueden resultar desconocidas para algunos
desarrolladores, Estas dos directivas using :
C#
using DateMeasurements =
System.Collections.Generic.Dictionary<System.DateTime,
IndexersSamples.Common.Measurements>;
Estas instrucciones crean un alias para un tipo genérico construido y permiten que el
código use después los nombres DateMeasurements y CityDataMeasurements (más
descriptivos) en vez de la construcción genérica de Dictionary<DateTime, Measurements>
y Dictionary<string, Dictionary<DateTime, Measurements> > .
Esta construcción requiere
el uso de los nombres completos de tipo en el lado derecho del signo = .
La segunda técnica consiste en quitar las partes de tiempo de cualquier objeto DateTime
usado para indizarse en las colecciones. .NET no incluye un tipo de solo fecha.
Los
desarrolladores usan el tipo DateTime , aunque emplean la propiedad Date para
asegurarse de que cualquier objeto DateTime de ese día sea igual.
Resumen
Debe crear indizadores siempre que tenga un elemento de propiedad en la clase, en la
que dicha propiedad no representa un valor único, sino una serie de valores donde cada
elemento se identifica mediante un conjunto de argumentos. Estos argumentos
únicamente pueden identificar el elemento al que se debe hacer referencia en la
colección.
Los indizadores amplían el concepto de las propiedades, en las que un
miembro se trata como un elemento de datos desde fuera de la clase, pero como un
método desde dentro. Los indizadores permiten que los argumentos busquen un solo
elemento en una propiedad que representa un conjunto de elementos.
Iterators
Artículo • 15/02/2023 • Tiempo de lectura: 6 minutos
Prácticamente todos los programas que escriba tendrán alguna necesidad de recorrer
en iteración una colección. Va a escribir código que examine cada elemento de una
colección.
También va a crear métodos de iterador, que son los métodos que genera un iterador
para los elementos de esa clase. Un iterador es un objeto que atraviesa un contenedor,
especialmente las listas. Los iteradores se pueden usar para:
C#
Console.WriteLine(item?.ToString());
That's all. (Esto es todo) Para recorrer en iteración todo el contenido de una colección, la
instrucción foreach es todo lo que necesita. Pero la instrucción foreach no es mágica.
Depende de dos interfaces genéricas definidas en la biblioteca de .NET Core para
generar el código necesario para recorrer en iteración una colección: IEnumerable<T> e
IEnumerator<T> . Este mecanismo se explica con más detalle a continuación.
C#
Console.WriteLine(item?.ToString());
C#
yield return 0;
yield return 1;
yield return 2;
yield return 3;
yield return 4;
yield return 5;
yield return 6;
yield return 7;
yield return 8;
yield return 9;
El código anterior muestra instrucciones distintivas yield return para resaltar el hecho
de que se pueden usar varias instrucciones discretas yield return en un método de
iterador. Puede usar (y hágalo a menudo) otras construcciones de lenguaje para
simplificar el código de un método de iterador. La definición del método siguiente
genera la misma secuencia de números:
C#
int index = 0;
No tiene que elegir entre una y otra. Puede tener tantas instrucciones yield return
como sea necesario para satisfacer las necesidades del método:
C#
int index = 0;
index = 100;
asincrónica:
C#
int index = 0;
await Task.Delay(500);
await Task.Delay(500);
index = 100;
C#
int index = 0;
if (index++ % interval == 0)
C#
int index = 0;
if (index++ % interval == 0)
Hay una restricción importante en los métodos de iterador: no puede tener una
instrucción return y una instrucción yield return en el mismo método. El código
siguiente no se compilará:
C#
int index = 0;
var items = new int[] {100, 101, 102, 103, 104, 105, 106, 107, 108, 109
};
return items;
Puede modificar el último método ligeramente para usar yield return en todas partes:
C#
int index = 0;
var items = new int[] {100, 101, 102, 103, 104, 105, 106, 107, 108, 109
};
C#
if (getCollection == false)
else
return IteratorMethod();
int index = 0;
if (index % 2 == 1)
index++;
Observe los métodos anteriores. El primero usa la instrucción estándar return para
devolver una colección vacía o el iterador creado por el segundo método. El segundo
método usa la instrucción yield return para crear la secuencia solicitada.
Profundización en foreach
La instrucción foreach se expande en un elemento estándar que usa las interfaces
IEnumerable<T> e IEnumerator<T> para recorrer en iteración todos los elementos de una
C#
while (enumerator.MoveNext())
Console.WriteLine(item.ToString());
C#
try
while (enumerator.MoveNext())
Console.WriteLine(item.ToString());
finally
// dispose of enumerator.
}
C#
try
Console.WriteLine(item.ToString());
finally
C#
finally
(enumerator as IDisposable)?.Dispose();
C#
finally
await asyncDisposable.DisposeAsync();
C#
finally
C#
finally
((IDisposable)enumerator).Dispose();
En todos estos casos, el método Sort() hace básicamente lo mismo: organiza los
elementos en la lista en función de una comparación. El código que compara dos
estrellas es diferente para cada uno de los criterios de ordenación.
Como verá más adelante en esta serie, el código de C# que escriba para algoritmos
como este tiene seguridad de tipos. El compilador garantiza que los tipos coincidan con
los argumentos y los tipos de valor devuelto.
El equipo aspiraba a crear una construcción de lenguaje común que pudiera usarse para
cualquier algoritmo de enlace en tiempo de ejecución. Los delegados permiten a los
desarrolladores aprender un concepto y usarlo en muchos problemas de software
diferentes.
En segundo lugar, el equipo quería que se admitiesen llamadas a métodos únicos y
multidifusión. (Los delegados de multidifusión son delegados que encadenan varias
llamadas de método. Verá ejemplos más adelante en esta serie).
El equipo quería que los delegados admitiesen la misma seguridad de tipos que los
desarrolladores esperan de todas las construcciones C#.
El resultado de todo ese trabajo fue la compatibilidad con los delegados y los eventos
en C# y .NET.
Los artículos restantes de esta serie tratarán sobre las características del lenguaje, la
compatibilidad con bibliotecas y las expresiones comunes que se usan al trabajar con
los delegados y eventos.
Obtendrá información sobre:
Comencemos.
Siguiente
System.Delegate y la palabra clave
delegate
Artículo • 15/02/2023 • Tiempo de lectura: 7 minutos
Anterior
En este artículo se tratan las clases de .NET que admiten delegados y sobre cómo se
asignan a la palabra clave delegate .
Para definir un tipo de delegado, se usa una sintaxis similar a la definición de una firma
de método. Solo hace falta agregar la palabra clave delegate a la definición.
Vamos a usar el método List.Sort() como ejemplo. El primer paso consiste en crear un
tipo para el delegado de comparación:
C#
El compilador genera una clase derivada de System.Delegate que coincide con la firma
usada (en este caso, un método que devuelve un entero y tiene dos argumentos). El tipo
de ese delegado es Comparison . El tipo de delegado Comparison es un tipo genérico.
Aquí puede obtener más información sobre los genéricos.
Observe que puede parecer que la sintaxis declara una variable, pero en realidad declara
un tipo. Puede definir tipos de delegado dentro de clases, directamente dentro de
espacios de nombres o incluso en el espacio de nombres global.
7 Nota
No se recomienda declarar tipos de delegado (u otros tipos) directamente en el
espacio de nombres global.
C#
El fragmento de código anterior declara una variable de miembro dentro de una clase.
También puede declarar variables de delegado que sean variables locales o argumentos
para los métodos.
Invocación de delegados
Para invocar los métodos que se encuentran en la lista de invocación de un delegado,
llame a dicho delegado. Dentro del método Sort() , el código llamará al método de
comparación para determinar en qué orden colocará los objetos:
C#
Los programadores que quieran usar el método List.Sort() deben definir un método
cuya firma coincida con la definición del tipo de delegado y asignarlo al delegado usado
por el método de ordenación. Esta asignación agrega el método a la lista de invocación
de ese objeto de delegado.
Supongamos que quiera ordenar una lista de cadenas por su duración. La función de
comparación podría ser la siguiente:
C#
left.Length.CompareTo(right.Length);
El método se ha declarado como un método privado. Esto es correcto, ya que tal vez no
le interese que este método forme parte de la interfaz pública. Aun así, puede usarse
como método de comparación cuando se asocia a un delegado. El código de llamada
tendrá este método asociado a la lista de destino del objeto de delegado y puede tener
acceso a él a través de ese delegado.
C#
phrases.Sort(CompareLength);
Observe que se usa el nombre del método sin paréntesis. Al usar el método como un
argumento, le indica al compilador que convierta la referencia del método en una
referencia que se puede usar como un destino de invocación del delegado y que asocie
ese método como un destino de invocación.
También podría haber declarado de forma explícita una variable de tipo
Comparison<string> y realizado una asignación:
C#
phrases.Sort(comparer);
En los casos en los que el método que se usa como destino del delegado es un método
pequeño, es habitual usar la sintaxis de expresión lambda para realizar la asignación:
C#
phrases.Sort(comparer);
El uso de expresiones lambda para destinos de delegados se explica con más detalle en
una sección posterior.
Este diseño tiene sus orígenes en la primera versión de C# y. NET. Uno de los objetivos
del equipo de diseño era asegurarse de que el lenguaje aplicaba seguridad de tipos al
usar delegados. Esto significaba que debían asegurarse no solo de que los delegados se
invocaban con el tipo y el número adecuados de argumentos, sino de que todos los
tipos de valor devueltos se indicaban correctamente en tiempo de compilación. Los
delegados formaban parte de la versión 1.0 .NET, que apareció antes que los genéricos.
La mejor manera de aplicar la seguridad de tipos era que el compilador crease las clases
de delegado concretas que representasen la firma del método que se usaba.
Aunque usted no puede crear clases derivadas directamente, usará los métodos
definidos en estas clases. Vamos a ver los métodos más comunes que usará al trabajar
con delegados.
Lo primero que debe recordar es que cada delegado con el que trabaje se deriva de
MulticastDelegate . Un delegado multidifusión significa que se puede invocar más de un
destino de método al invocar en un delegado. El diseño original consideraba la
posibilidad de establecer una distinción entre los delegados en los que solo se podía
asociar e invocar un método de destino y los delegados en los que se podía asociar e
invocar varios métodos de destino. Esa distinción resultó ser menos útil en la práctica de
lo que se pensaba. Las dos clases ya estaban creadas y se han conservado en el marco
de trabajo desde la versión pública inicial.
Los métodos que usará más a menudo con los delegados son Invoke() y BeginInvoke()
/ EndInvoke() . Invoke() invocará todos los métodos que se han asociado a una
instancia de delegado en concreto. Como ya hemos visto anteriormente, por lo general
los delegados se invocan mediante la sintaxis de llamada de método en la variable de
delegado. Como verá más adelante en esta serie, hay patrones que funcionan
directamente con estos métodos.
Ahora que ha visto la sintaxis del lenguaje y las clases que admiten delegados,
examinemos cómo se usan, se crean y se invocan delegados fuertemente tipados.
Siguiente
Delegados fuertemente tipados
Artículo • 04/01/2023 • Tiempo de lectura: 3 minutos
Anterior
En el artículo anterior pudo ver que con la palabra clave delegate se crean tipos de
delegados concretos.
En la práctica, esto daría lugar a la creación de nuevos tipos de delegado siempre que
necesitara otra firma de método. Este trabajo podría resultar tedioso pasado un tiempo.
Cada nueva característica exige nuevos tipos de delegado.
Afortunadamente, esto no es necesario. .NET Core Framework contiene varios tipos que
puede volver a usar siempre que necesite tipos de delegado. Son definiciones genéricas,
por lo que puede declarar personalizaciones cuando necesite nuevas declaraciones de
método.
C#
Hay variaciones del delegado Action que contienen hasta 16 argumentos, como
Action<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16>.
Es importante que
estas definiciones usen argumentos genéricos distintos para cada uno de los
argumentos del delegado: eso proporciona la máxima flexibilidad. Los argumentos de
método no tienen que ser, aunque pueden ser, del mismo tipo.
Use uno de los tipos Action para cualquier tipo de delegado que tenga un tipo de valor
devuelto void.
.NET Framework también incluye varios tipos de delegado genéricos que se pueden usar
para los tipos de delegado que devuelven valores:
C#
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
El modificador out del argumento de tipo genérico result se trata en el artículo sobre la
covarianza.
Hay variaciones del delegado Func con hasta 16 argumentos de entrada, como
Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,TResult>.
Por convención,
el tipo del resultado siempre es el último parámetro de tipo de todas las declaraciones
Func .
Use uno de los tipos Func para cualquier tipo de delegado que devuelva un valor.
También hay un tipo Predicate<T> especializado para un delegado que devuelva una
prueba en un valor único:
C#
Es posible que observe que para cualquier tipo Predicate existe un tipo Func
estructuralmente equivalente, por ejemplo:
C#
Predicate<string> AnotherTestForString;
Podría llegar a pensar que estos dos tipos son equivalentes, pero no lo son.
Estas dos
variables no se pueden usar indistintamente. A una variable de un tipo no se le puede
asignar el otro tipo. El sistema de tipos de C# usa los nombres de los tipos definidos, no
la estructura.
Esto debería ahorrar tiempo y minimizar el número de nuevos tipos que es necesario
crear para poder trabajar con delegados.
En el siguiente artículo se verán varios patrones comunes para trabajar con delegados
en la práctica.
Siguiente
Patrones comunes para delegados
Artículo • 15/02/2023 • Tiempo de lectura: 8 minutos
Anterior
Los delegados proporcionan un mecanismo que permite que los diseños de software
supongan un acoplamiento mínimo entre los componentes.
C#
Se filtra la secuencia solo de los números que son inferiores al valor 10.
El método
Where usa un delegado que determina qué elementos de una secuencia pasan el filtro.
Cuando crea una consulta LINQ, proporciona la implementación del delegado para este
fin específico.
C#
Este ejemplo se repite con todos los métodos que forman parte de LINQ. Todos se
basan en delegados para el código que administra la consulta específica. Este modelo
de diseño de API es eficaz para obtener información y comprender.
En este ejemplo sencillo se ilustra cómo los delegados necesitan muy poco
acoplamiento entre componentes. No necesita crear una clase que derive de una clase
base determinada. No necesita implementar una interfaz específica.
El único requisito
consiste en proporcionar la implementación de un método que sea fundamental para la
tarea que nos ocupa.
Con este diseño, el componente de registro principal puede ser una clase no virtual,
incluso sellada. Puede conectar cualquier conjunto de delegados para escribir los
mensajes en un medio de almacenamiento diferente. La compatibilidad integrada para
los delegados multidifusión facilita la compatibilidad de escenarios donde los mensajes
deben escribirse en varias ubicaciones (un archivo y una consola).
C#
WriteMessage(msg);
La clase estática anterior es lo más sencillo que puede funcionar. Necesitamos escribir
solo la implementación para el método que escribe mensajes en la consola:
C#
Console.Error.WriteLine(message);
C#
Logger.WriteMessage += LoggingMethods.LogToConsole;
Procedimientos
Hasta ahora nuestro ejemplo es bastante sencillo, pero sigue mostrando algunas
instrucciones importantes para los diseños que involucran a los delegados.
Con los tipos de delegado definidos en el marco de trabajo principal es más sencillo
para los usuarios trabajar con los delegados. No necesita definir tipos nuevos, y los
desarrolladores que usen su biblioteca no necesitan aprender nuevos tipos de delegado
especializados.
Las interfaces que se han usado son tan mínimas y flexibles como es posible: para crear
un registrador de salida nuevo, debe crear un método. Ese método puede ser un
método estático o un método de instancia. Puede tener cualquier acceso.
Formato de salida
Vamos a hacer esta primera versión un poco más sólida y, después, empezaremos a
crear otros mecanismos de registro.
Después, vamos a agregar algunos argumentos al método LogMessage() de manera que
su clase de registro cree más mensajes estructurados:
C#
Verbose,
Trace,
Information,
Warning,
Error,
Critical
C#
WriteMessage(outputMsg);
A continuación, vamos a usar ese argumento Severity para filtrar los mensajes que se
envían a la salida del registro.
C#
if (s < LogLevel)
return;
WriteMessage(outputMsg);
Procedimientos
Ha agregado características nuevas a la infraestructura de registro. Como el
componente del registrador se acopla débilmente a cualquier mecanismo de salida,
estas características nuevas pueden agregarse sin afectar a ningún código que
implementa el delegado del registrador.
A medida que siga creando esto, verá más ejemplos de cómo este acoplamiento débil
permite una mayor flexibilidad en la actualización de las partes del sitio sin que haya
cambios en otras ubicaciones. De hecho, en una aplicación más grande, las clases de
salida del registrador pueden estar en un ensamblado diferente, y ni siquiera necesitan
volver a crearse.
C#
logPath = path;
Logger.WriteMessage += LogMessage;
try
log.WriteLine(msg);
log.Flush();
catch (Exception)
Una vez que haya creado esta clase, puede inicializarla y esta asocia su método
LogMessage al componente de registrador:
C#
Estos dos no son mutuamente exclusivos. Puede asociar ambos métodos de registro y
generar mensajes en la consola y en un archivo:
C#
Después, incluso en la misma aplicación, puede quitar uno de los delegados sin
ocasionar ningún otro problema en el sistema:
C#
Logger.WriteMessage -= LoggingMethods.LogToConsole;
Procedimientos
Ahora, ha agregado un segundo controlador de salida para el subsistema de registro.
Este necesita un poco más de infraestructura para admitir correctamente el sistema de
archivos. El delegado es un método de instancia. También es un método privado.
No
existe ninguna necesidad de una mayor accesibilidad porque la infraestructura de
delegado puede conectarse a los delegados.
En segundo lugar, el diseño basado en delegados permite varios métodos de salida sin
ningún código adicional. No necesita crear ninguna infraestructura adicional para
admitir varios métodos de salida. Simplemente se convierten en otro método en la lista
de invocación.
Preste una atención especial al código del método de salida de registro de archivo. Se
codifica para garantizar que no produce ninguna excepción. Aunque esto no siempre es
estrictamente necesario, a menudo es un buen procedimiento. Si cualquiera de los
métodos de delegado produce una excepción, los delegados restantes que se
encuentran en la invocación no se invocarán.
Ninguna parte del código de la clase Logger tendrá que actualizarse para admitir
cualquiera de los escenarios.
C#
WriteMessage?.Invoke(msg);
Siguiente
Introducción a los eventos
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
Anterior
Los eventos son, como los delegados, un mecanismo de enlace en tiempo de ejecución.
De hecho, los eventos se crean con compatibilidad de lenguaje para los delegados.
Los eventos son una manera para que un objeto difunda (a todos los componentes
interesados del sistema) que algo ha sucedido. Cualquier otro componente puede
suscribirse al evento, y recibir una notificación cuando se genere uno.
Puede definir eventos que deben generarse para las clases. Una consideración
importante a la hora de trabajar con eventos es que puede que no haya ningún objeto
registrado para un evento determinado. Debe escribir el código de manera que no
genere eventos cuando no esté configurado ningún agente de escucha.
La suscripción a un evento también crea un acoplamiento entre dos objetos (el origen
del evento y el receptor del evento). Necesita asegurarse de que el receptor del evento
cancela la suscripción del origen del evento cuando ya no está interesado en eventos.
C#
C#
Console.WriteLine(eventArgs.FoundFile);
fileLister.Progress += onProgress;
El método de controlador normalmente tiene el prefijo "On" seguido del nombre del
evento, como se ha mostrado anteriormente.
C#
fileLister.Progress -= onProgress;
Es importante que declare una variable local para la expresión que representa el
controlador de eventos. Eso garantiza que la cancelación de la suscripción quita el
controlador.
Si, en su lugar, ha usado el cuerpo de la expresión lambda, está intentando
quitar un controlador que nunca ha estado asociado, lo que no produce ninguna acción.
En el artículo siguiente, obtendrá más información sobre los modelos de eventos típicos
y las diferentes variaciones de este ejemplo.
Siguiente
Patrón de eventos estándar de .NET
Artículo • 15/02/2023 • Tiempo de lectura: 9 minutos
Anterior
Los eventos de .NET generalmente siguen unos patrones conocidos. Estandarizar sobre
estos patrones significa que los desarrolladores pueden aprovechar el conocimiento de
esos patrones estándar, que se pueden aplicar a cualquier programa de evento de .NET.
Vamos a analizar los patrones estándar, para que tenga todos los conocimientos
necesarios para crear orígenes de eventos estándar y suscribirse y procesar eventos
estándar en el código.
C#
El tipo de valor devuelto es void. Los eventos se basan en delegados y son delegados de
multidifusión. Eso admite varios suscriptores para cualquier origen de eventos. El único
valor devuelto de un método no escala a varios suscriptores de eventos. ¿Qué valor
devuelto ve el origen de evento después de generar un evento? Más adelante en este
artículo verá cómo crear protocolos de evento que admiten suscriptores de eventos que
notifican información al origen del evento.
Vamos a crear una clase que enumera los archivos en un directorio, o cualquiera de sus
subdirectorios que siguen un patrón. Este componente genera un evento para cada
archivo encontrado que coincida con el modelo.
Esta es la declaración del argumento de evento inicial para buscar un archivo buscado:
C#
Aunque este tipo parece un tipo pequeño exclusivo para datos, debe seguir la
convención y convertirlo en un tipo de referencia ( class ). Esto significa que el objeto de
argumento se pasará por referencia y que todos los suscriptores verán las
actualizaciones de los datos. La primera versión es un objeto inmutable. Es preferible
hacer que las propiedades en el tipo de argumento de evento sean inmutables. De ese
modo, un suscriptor no puede cambiar los valores antes de que los vea otro suscriptor.
(Hay excepciones, como verá a continuación).
Vamos a rellenar la clase FileSearcher para buscar archivos que coincidan con un patrón
y generar el evento correcto cuando se detecte una coincidencia.
C#
RaiseFileFound(file);
}
C#
Parece que se está declarando un campo público, lo que podría parecer una práctica
orientada a objetos incorrecta. Quiere proteger el acceso a los datos a través de
propiedades o métodos. Aunque esto puede parecer una mala práctica, el código
generado por el compilador crea contenedores para que solo se pueda acceder de
forma segura a los objetos de evento. Las únicas operaciones disponibles en un evento
con aspecto de campo son las de agregar controlador:
C#
int filesFound = 0;
Console.WriteLine(eventArgs.FoundFile);
filesFound++;
};
fileLister.FileFound += onFileFound;
y quitar controlador:
C#
fileLister.FileFound -= onFileFound;
Tenga en cuenta que hay una variable local para el controlador. Si usó el cuerpo de la
expresión lambda, la eliminación no funcionará correctamente. Sería una instancia
diferente del delegado y, en modo silencioso, no se hace nada.
El código fuera de la clase no puede generar el evento, ni puede realizar otras
operaciones.
Cuando se genera el evento encontrado, los agentes de escucha deberían ser capaces
de detener el procesamiento, si este archivo es el último que se busca.
Existen dos patrones diferentes que podrían usarse, basándose en la semántica del
contrato de cancelación. En ambos casos, se agrega un campo booleano a
EventArguments para el evento del archivo encontrado.
Uno de los patrones permitiría a cualquier suscriptor cancelar la operación. Para este
patrón, el nuevo campo se inicializa en false . Los suscriptores pueden cambiarlo a
true . Después de que todos los suscriptores hayan visto el evento generado, el
El segundo patrón solo debería cancelar la operación si todos los suscriptores quieren
que se cancele. En este patrón, el nuevo campo se inicializa para indicar que se debe
cancelar la operación y cualquier suscriptor puede modificarlo para indicar que la
operación debe continuar. Después de que todos los suscriptores hayan visto el evento
generado, el componente FileSearcher examina el valor booleano y toma medidas. Hay
un paso adicional en este patrón: el componente necesita saber si los suscriptores
vieron el evento. Si no hay ningún suscriptor, el campo indicaría incorrectamente una
cancelación.
C#
C#
if (args.CancelRequested)
{
break;
FileFound?.Invoke(this, args);
return args;
Una ventaja de este patrón es que no supone un cambio brusco. Ninguno de los
suscriptores solicitó una cancelación antes y siguen sin hacerlo. No debe actualizarse el
código de ningún suscriptor a menos que quieran admitir el nuevo protocolo de
cancelación. Está acoplado muy holgadamente.
Vamos a actualizar el suscriptor para que solicite una cancelación una vez que encuentra
el primer ejecutable:
C#
Console.WriteLine(eventArgs.FoundFile);
eventArgs.CancelRequested = true;
};
Podría llegar a ser una operación de larga duración si el directorio tuviese muchos
subdirectorios. Vamos a agregar un evento que se genera cuando comienza cada nueva
búsqueda en el directorio. Esto permite a los suscriptores realizar el seguimiento y
actualizar al usuario sobre el progreso. Todos los ejemplos creados hasta ahora son
públicos. Convertiremos este evento en un evento interno. Eso significa que también se
pueden convertir en internos los tipos que se usan para los argumentos.
Comenzará creando la nueva clase derivada de EventArgs para informar del nuevo
directorio y del progreso.
C#
CurrentSearchDirectory = dir;
TotalDirs = totalDirs;
CompletedDirs = completedDirs;
De nuevo, puede seguir las recomendaciones para crear un tipo de referencia inmutable
para los argumentos de evento.
Después, defina el evento. Esta vez, usará una sintaxis diferente. Además de usar la
sintaxis de campos, puede crear explícitamente la propiedad con controladores add y
remove. En este ejemplo, no necesitará código adicional en los controladores, pero aquí
se muestra cómo se crean.
C#
En muchos aspectos, el código que se escribe aquí refleja el código que genera el
compilador para las definiciones de evento de campo que se vieron anteriormente. El
evento se crea mediante una sintaxis muy similar a la que se usó para las propiedades.
Tenga en cuenta que los controladores tienen nombres diferentes: add y remove . Se
llaman para suscribirse al evento o para cancelar la suscripción al evento. Tenga en
cuenta que también debe declarar un campo de respaldo privado para almacenar la
variable de evento. Se inicializa en null.
Después, se agregará la sobrecarga del método Search que recorre los subdirectorios y
genera los dos eventos. La manera más fácil de hacerlo consiste en usar un argumento
predeterminado para especificar que se quiere buscar en todos los directorios:
C#
if (searchSubDirs)
var completedDirs = 0;
// Search 'dir' and its subdirectories for files that match the
search pattern:
SearchDirectory(dir, searchPattern);
SearchDirectory(directory, searchPattern);
else
SearchDirectory(directory, searchPattern);
if (args.CancelRequested)
{
break;
_directoryChanged?.Invoke(
this,
FileFound?.Invoke(this, args);
return args;
correctamente.
Vamos a agregar un controlador para escribir una línea que muestre el progreso en la
ventana de la consola.
C#
Console.Write($"Entering '{eventArgs.CurrentSearchDirectory}'.");
};
Ha visto los patrones que se siguen en todo el ecosistema de. NET. El aprendizaje de
estos patrones y convenciones le permitirá escribir elementos de C# y .NET
rápidamente.
Vea también
Introducción a los eventos
Diseño de eventos
Control y generación de eventos
Más adelante verá algunos cambios en estos patrones en la versión más reciente de.
NET.
Siguiente
Patrón de eventos actualizado de .NET
Core
Artículo • 14/02/2023 • Tiempo de lectura: 4 minutos
Anterior
En el artículo anterior se describían los patrones de eventos más comunes. .NET Core
tiene un patrón menos estricto. En esta versión, la definición EventHandler<TEventArgs>
ya no tiene la restricción que obliga a que TEventArgs sea una clase derivada de
System.EventArgs .
exactamente igual.
C#
CurrentSearchDirectory = dir;
TotalDirs = totalDirs;
CompletedDirs = completedDirs;
Ahora veamos cómo este cambio puede ser compatible con versiones anteriores.
La
eliminación de la restricción no afecta al código existente. Los tipos de argumento de
evento existentes siguen derivando de System.EventArgs .
La compatibilidad con
versiones anteriores es uno de los motivos principales por los que siguen derivando de
System.EventArgs . Los suscriptores de eventos existentes serán suscriptores a un evento
Según una lógica similar, cualquier tipo de argumento de evento creado ahora no
tendría ningún suscriptor en el código base existente. Los nuevos tipos de evento que
no deriven de System.EventArgs no interrumpirán ese código base.
C#
try
await DoWorkAsync();
catch (Exception e)
};
Por eso debe encapsular la instrucción await para la tarea asincrónica en su propio
bloque try. Si esto genera una tarea con error, puede registrar el error. Si se produce un
error del que no se puede recuperar la aplicación, puede salir del programa de forma
rápida y correctamente.
Estas son las principales actualizaciones del patrón de eventos de .NET. Verá numerosos
ejemplos de las versiones anteriores de las bibliotecas con las que trabaje. Aun así,
también debe entender los patrones más recientes.
Siguiente
Distinción de delegados y eventos
Artículo • 04/01/2023 • Tiempo de lectura: 3 minutos
Anterior
Los desarrolladores que son nuevos en la plataforma de NET Core a menudo tienen
problemas para decidir entre un diseño basado en delegates y uno basado en events .
La elección de delegados o eventos suele ser difícil, ya que las dos características de
lenguaje son similares. Los eventos incluso se crean con compatibilidad de lenguaje
para los delegados.
Con todas estas similitudes, es fácil tener problemas para determinar cuándo usar cada
uno.
Tenga en cuenta los ejemplos que se crean en esta sección. Al código que ha creado
con List.Sort() se le debe proporcionar una función de comparador para ordenar los
elementos de manera adecuada. Las consultas LINQ deben proporcionarse con
delegados para determinar qué elementos se van a devolver. Ambos han usado un
diseño creado con delegados.
Observe que estas dos heurísticas suelen estar presentes: si el método delegado
devuelve un valor, lo más probable es que afecte de algún modo al algoritmo.
Compare eso con muchos diseños basados en delegados, donde un delegado se usa
como un argumento para un método, y el delegado no se usa después de que se
devuelva ese método.
Evaluar cuidadosamente
Las consideraciones anteriores no son reglas rápidas ni estrictas. En su lugar,
representan instrucciones que pueden ayudarle a decidir qué opción es mejor para su
uso particular. Como son similares, incluso puede crear un prototipo de los dos y
considerar con cuál sería más natural trabajar. Ambos controlan escenarios de enlace en
tiempo de ejecución correctamente. Use el que comunique mejor su diseño.
Language-Integrated Query (LINQ)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
Para un desarrollador que escribe consultas, la parte más visible de "lenguaje integrado"
de LINQ es la expresión de consulta. Las expresiones de consulta se escriben con una
sintaxis de consulta declarativa. Con la sintaxis de consulta, puede realizar operaciones
de filtrado, ordenación y agrupamiento en orígenes de datos con el mínimo código.
Utilice los mismos patrones de expresión de consulta básica para consultar y
transformar datos de bases de datos SQL, conjuntos de datos de ADO .NET, secuencias
y documentos XML y colecciones. NET.
C#
IEnumerable<int> scoreQuery =
select score;
// Output: 97 92 81
Pasos siguientes
Para obtener más información sobre LINQ, empiece a familiarizarse con algunos
conceptos básicos en Conceptos básicos de las expresiones de consultas y, después, lea
la documentación de la tecnología de LINQ en la que esté interesado:
Para comprender mejor los aspectos generales de LINQ, vea LINQ in C# (LINQ en C#).
Para empezar a trabajar con LINQ en C#, vea el tutorial Trabajar con LINQ.
Conceptos básicos de las expresiones de
consultas
Artículo • 15/02/2023 • Tiempo de lectura: 13 minutos
En este artículo se presentan los conceptos básicos relacionados con las expresiones de
consulta en C#.
Por lo general, los datos de origen se organizan lógicamente como una secuencia de
elementos del mismo tipo. Por ejemplo, una tabla de base de datos SQL contiene una
secuencia de filas. En un archivo XML, hay una "secuencia" de elementos XML (aunque
estos se organizan jerárquicamente en una estructura de árbol). Una colección en
memoria contiene una secuencia de objetos.
Dada esta secuencia de origen, una consulta puede hacer una de estas tres cosas:
Recuperar un subconjunto de los elementos para generar una nueva secuencia sin
modificar los elementos individuales. Después, la consulta puede ordenar o
agrupar la secuencia devuelta de varias maneras, como se muestra en el ejemplo
siguiente (supongamos que scores es int[] ):
C#
IEnumerable<int> highScoresQuery =
select score;
C#
IEnumerable<string> highScoresQuery2 =
C#
int highScoreCount = (
select score
).Count();
C#
IEnumerable<int> highScoresQuery3 =
select score;
Una expresión de consulta debe comenzar con una cláusula from y debe terminar con
una cláusula select o group. Entre la primera cláusula from y la última cláusula select o
group , puede contener una o varias de estas cláusulas opcionales: where, orderby, join,
let e incluso cláusulas from adicionales. También puede usar la palabra clave into para
que el resultado de una cláusula join o group actúe como el origen de las cláusulas de
consulta adicionales en la misma expresión de consulta.
Variable de consulta
En LINQ, una variable de consulta es cualquier variable que almacene una consulta en
lugar de los resultados de una consulta. Más concretamente, una variable de consulta es
siempre un tipo enumerable que generará una secuencia de elementos cuando se itere
en una instrucción foreach o en una llamada directa a su método
IEnumerator.MoveNext .
C#
// Data source.
// Query Expression.
Console.WriteLine(testScore);
}
// Output: 93 90 82 82
C#
//Query syntax
IEnumerable<City> queryMajorCities =
select city;
// Method-based syntax
Por otro lado, en los dos ejemplos siguientes se muestran variables que no son de
consulta, a pesar de que se inicialicen con una consulta. No son variables de consulta
porque almacenan resultados:
C#
int highestScore = (
select score
).Max();
IEnumerable<int> scoreQuery =
select score;
highScore = scores.Max();
C#
List<City> largeCitiesList = (
select city
).ToList();
IEnumerable<City> largeCitiesQuery =
select city;
Para obtener más información sobre las distintas formas de expresar consultas, vea
Query syntax and method syntax in LINQ (Sintaxis de consulta y sintaxis de método en
LINQ).
C#
var queryCities =
select city;
Para obtener más información, vea Implicitly typed local variables (Variables locales con
asignación implícita de tipos) y Type relationships in LINQ query operations (Relaciones
entre tipos en las operaciones de consulta de LINQ).
C#
IEnumerable<Country> countryAreaQuery =
select country;
La variable de rango está en el ámbito hasta que se cierra la consulta con un punto y
coma o con una cláusula de continuación.
Una expresión de consulta puede contener varias cláusulas from . Use más cláusulas
from cuando cada elemento de la secuencia de origen sea una colección en sí mismo o
contenga una colección. Por ejemplo, supongamos que tiene una colección de objetos
Country , cada uno de los cuales contiene una colección de objetos City denominados
Cities . Para consultar los objetos City de cada Country , use dos cláusulas from , como
se muestra aquí:
C#
IEnumerable<City> cityQuery =
select city;
group (cláusula)
Use la cláusula group para generar una secuencia de grupos organizados por la clave
que especifique. La clave puede ser cualquier tipo de datos. Por ejemplo, la siguiente
consulta crea una secuencia de grupos que contienen uno o más objetos Country y
cuya clave es un tipo char con un valor que es la primera letra del nombre de un país.
C#
var queryCountryGroups =
Para obtener más información sobre la agrupación, vea group clause (Cláusula group).
select (cláusula)
Use la cláusula select para generar todos los demás tipos de secuencias. Una cláusula
select simple solo genera una secuencia del mismo tipo de objetos que los objetos
C#
IEnumerable<Country> sortedQuery =
orderby country.Area
select country;
La cláusula select puede usarse para transformar los datos de origen en secuencias de
nuevos tipos. Esta transformación también se denomina proyección. En el ejemplo
siguiente, la cláusula select proyecta una secuencia de tipos anónimos que solo
contiene un subconjunto de los campos del elemento original. Tenga en cuenta que los
nuevos objetos se inicializan mediante un inicializador de objeto.
C#
var queryNameAndPop =
select new
Name = country.Name,
Pop = country.Population
};
Para obtener más información sobre todas las formas en que se puede usar una cláusula
select para transformar datos de origen, vea select clause (Cláusula select).
C#
var percentileQuery =
orderby countryGroup.Key
select countryGroup;
Console.WriteLine(grouping.Key);
where (cláusula)
Use la cláusula where para filtrar los elementos de los datos de origen en función de
una o varias expresiones de predicado. La cláusula where del ejemplo siguiente tiene un
predicado con dos condiciones.
C#
IEnumerable<City> queryCityPop =
select city;
orderby (cláusula)
Use la cláusula orderby para ordenar los resultados en orden ascendente o
descendente. También puede especificar criterios de ordenación secundaria. En el
ejemplo siguiente se realiza una ordenación primaria de los objetos country mediante
la propiedad Area . Después, se realiza una ordenación secundaria mediante la
propiedad Population .
C#
IEnumerable<Country> querySortedCountries =
select country;
C#
var categoryQuery =
select new
Category = cat,
Name = prod.Name
};
También puede realizar una combinación agrupada. Para ello, almacene los resultados
de la operación join en una variable temporal mediante el uso de la palabra clave into.
Para obtener más información, vea join (Cláusula, Referencia de C#).
let (cláusula)
Use la cláusula let para almacenar el resultado de una expresión, como una llamada de
método, en una nueva variable de rango. En el ejemplo siguiente, la variable de rango
firstName almacena el primer elemento de la matriz de cadenas devuelta por Split .
C#
IEnumerable<string> queryFirstNames =
select firstName;
C#
var queryGroupMax =
select new
Level = studentGroup.Key,
HighestScore = (
select student2.ExamScores.Average()
).Max()
};
Vea también
Guía de programación de C#
Language-Integrated Query (LINQ)
Palabras clave de consultas (LINQ)
Información general sobre operadores de consulta estándar
LINQ en C#
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Esta sección contiene vínculos a temas que ofrecen información más detallada sobre
LINQ.
En esta sección
Introducción a las consultas LINQ
Describe las tres partes de la operación de consulta LINQ básica que son comunes a
todos los lenguajes y orígenes de datos.
Ofrece una breve introducción a los tipos genéricos, tal como se usan en LINQ.
Describe cómo se mantienen o transforman los tipos en las tres partes de una operación
de consulta LINQ.
Secciones relacionadas
Expresiones de consulta LINQ
Incluye información general sobre las consultas en LINQ y ofrece vínculos a recursos
adicionales.
En este artículo se muestran las tres formas de escribir una consulta LINQ en C#:
Los ejemplos siguientes muestran algunas consultas LINQ sencillas mediante cada
enfoque enumerado anteriormente. En general, la regla es usar (1) siempre que sea
posible y usar (2) y (3) cuando sea necesario.
7 Nota
C#
// Query #1.
IEnumerable<int> filteringQuery =
select num;
// Query #2.
IEnumerable<int> orderingQuery =
select num;
// Query #3.
Tenga en cuenta que el tipo de las consultas es IEnumerable<T>. Todas estas consultas
podrían escribirse mediante var como se muestra en el ejemplo siguiente:
En cada ejemplo anterior, las consultas no se ejecutan realmente hasta que se recorre en
iteración la variable de consulta en una instrucción foreach o cualquier otra instrucción.
Para más información, vea Introduction to LINQ Queries (Introducción a las consultas
LINQ).
C#
List<int> numbers2 = new() { 15, 14, 11, 13, 19, 18, 16, 17, 12, 10 };
// Query #4.
// Query #5.
// Query #6.
Cada una de las consultas anteriores puede escribirse mediante tipos implícitos con var,
como se muestra en el ejemplo siguiente:
C#
C#
// Query #7.
int numCount1 = (
select num
).Count();
IEnumerable<int> numbersQuery =
select num;
Dado que la consulta número 7 devuelve un solo valor y no una colección, se ejecuta
inmediatamente.
La consulta anterior puede escribirse mediante tipos implícitos con var como sigue:
C#
C#
C#
Consulte también
Tutorial: Escribir consultas en C#
Language-Integrated Query (LINQ)
where (cláusula)
Consultar una colección de objetos
Artículo • 10/02/2023 • Tiempo de lectura: 2 minutos
En este tema se muestra un ejemplo de cómo realizar una consulta simple en una lista
de objetos Student . Cada objeto Student contiene información básica sobre el alumno
y una lista que representa las puntuaciones del alumno en cuatro exámenes.
7 Nota
Muchos otros ejemplos de esta sección usan la misma clase Student y colección
students .
C#
class Student
this.FirstName = FirstName;
this.LastName = LastName;
this.ID = ID;
this.Year = Year;
this.ExamScores = ExamScores;
this.FirstName = FirstName;
this.LastName = LastName;
ID = StudentID;
new(
Year: GradeLevel.SecondYear,
),
new(
GradeLevel.ThirdYear,
new() { 99, 86, 90, 94 }
),
new(
GradeLevel.FirstYear,
new() { 93, 92, 80, 87 }
),
new(
GradeLevel.FourthYear,
),
new(
GradeLevel.ThirdYear,
new() { 35, 72, 91, 70 }
),
new(
GradeLevel.SecondYear,
),
new(
GradeLevel.FirstYear,
new() { 88, 94, 65, 91 }
),
new(
GradeLevel.FourthYear,
),
new(
GradeLevel.SecondYear,
),
new(
GradeLevel.ThirdYear,
new() { 68, 79, 88, 92 }
),
new(
GradeLevel.FirstYear,
new() { 94, 92, 91, 91 }
),
new(
GradeLevel.FourthYear,
};
enum GradeLevel
FirstYear = 1,
SecondYear,
ThirdYear,
FourthYear
};
Ejemplo
La consulta siguiente devuelve los alumnos que reciben una puntuación de 90 o más en
su primer examen.
C#
var highScores =
select new
Name = student.FirstName,
Score = student.ExamScores[exam]
};
Console.WriteLine($"{item.Name,-15}{item.Score}");
QueryHighScores(1, 90);
Vea también
Language-Integrated Query (LINQ)
Interpolación de cadenas
Cómo devolver una consulta de un
método
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
En este ejemplo se muestra cómo devolver una consulta desde un método como un
valor devuelto y como un parámetro out .
Los objetos de consulta admiten composición, lo que significa que puede devolver una
consulta desde un método. Los objetos que representan consultas no almacenan la
colección resultante, sino los pasos para generar los resultados cuando sea necesario. La
ventaja de devolver objetos de consulta desde métodos es que se pueden componer o
modificar todavía más. Por lo tanto, cualquier valor devuelto o parámetro out de un
método que devuelve una consulta también debe tener ese tipo. Si un método
materializa una consulta en un tipo concreto List<T> o Array, se considera que está
devolviendo los resultados de la consulta en lugar de la propia consulta. Una variable de
consulta que se devuelve desde un método sigue pudiendo componerse o modificarse.
Ejemplo
En el ejemplo siguiente, el primer método devuelve una consulta como un valor
devuelto y el segundo método devuelve una consulta como un parámetro out . Tenga
en cuenta que, en ambos casos, se trata de una consulta que se devuelve, no de los
resultados de la consulta.
C#
from i in ints
where i > 4
select i.ToString();
returnQ =
from i in ints
where i < 4
select i.ToString();
int[] nums = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Console.WriteLine(s);
// Rest the mouse pointer over the call to QueryMethod1 to see its
// return type.
Console.WriteLine(s);
Console.WriteLine(s);
// You can modify a query by using query composition. In this case, the
// previous query object is used to create a new query object; this new
object
myQuery1 =
select item;
Console.WriteLine(s);
Vea también
Language-Integrated Query (LINQ)
Almacenar los resultados de una
consulta en memoria
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
ToList
ToArray
ToDictionary
ToLookup
Ejemplo
C#
IEnumerable<int> queryFactorsOfFour =
where num % 4 == 0
select num;
// Read and write from the newly created list to demonstrate that it holds
data.
Console.WriteLine(factorsofFourList[2]);
factorsofFourList[2] = 0;
Console.WriteLine(factorsofFourList[2]);
Vea también
Language-Integrated Query (LINQ)
Agrupar los resultados de consultas
Artículo • 11/02/2023 • Tiempo de lectura: 5 minutos
La agrupación es una de las capacidades más eficaces de LINQ. Los ejemplos siguientes
muestran cómo agrupar datos de varias maneras:
Además, las dos últimas consultas proyectan sus resultados en un nuevo tipo anónimo
que solo contiene el nombre y los apellidos del estudiante. Para obtener más
información, vea la cláusula group.
7 Nota
En los ejemplos de este tema se usa la clase Student y la lista students del código
de ejemplo incluido en Consulta de una colección de objetos.
C#
// DataClass.Student>>.
var groupByLastNamesQuery =
orderby newGroup.Key
select newGroup;
Console.WriteLine($"Key: {nameGroup.Key}");
Console.WriteLine($"\t{student.LastName}, {student.FirstName}");
/* Output:
Key: Adams
Adams, Terry
Key: Fakhouri
Fakhouri, Fadi
Key: Feng
Feng, Hanying
Key: Garcia
Garcia, Cesar
Garcia, Debra
Garcia, Hugo
Key: Mortensen
Mortensen, Sven
Key: O'Donnell
O'Donnell, Claire
Key: Omelchenko
Omelchenko, Svetlana
Key: Tucker
Tucker, Lance
Tucker, Michael
Key: Zabokritski
Zabokritski, Eugene
*/
C#
var groupByFirstLetterQuery =
Console.WriteLine($"Key: {studentGroup.Key}");
Console.WriteLine($"\t{student.LastName}, {student.FirstName}");
/* Output:
Key: A
Adams, Terry
Key: F
Fakhouri, Fadi
Feng, Hanying
Key: G
Garcia, Cesar
Garcia, Debra
Garcia, Hugo
Key: M
Mortensen, Sven
Key: O
O'Donnell, Claire
Omelchenko, Svetlana
Key: T
Tucker, Lance
Tucker, Michael
Key: Z
Zabokritski, Eugene
*/
C#
int GetPercentile(Student s)
var groupByPercentileQuery =
group new
student.FirstName,
student.LastName
orderby percentGroup.Key
select percentGroup;
Console.WriteLine($"\t{item.LastName}, {item.FirstName}");
/* Output:
Key: 60
Garcia, Debra
Key: 70
O'Donnell, Claire
Key: 80
Adams, Terry
Feng, Hanying
Garcia, Cesar
Garcia, Hugo
Mortensen, Sven
Omelchenko, Svetlana
Tucker, Lance
Zabokritski, Eugene
Key: 90
Fakhouri, Fadi
Tucker, Michael
*/
C#
var groupByHighAverageQuery =
group new
student.FirstName,
student.LastName
select studentGroup;
Console.WriteLine($"Key: {studentGroup.Key}");
Console.WriteLine($"\t{student.FirstName} {student.LastName}");
/* Output:
Key: True
Terry Adams
Fadi Fakhouri
Hanying Feng
Cesar Garcia
Hugo Garcia
Sven Mortensen
Svetlana Omelchenko
Lance Tucker
Michael Tucker
Eugene Zabokritski
Key: False
Debra Garcia
Claire O'Donnell
*/
C#
var groupByCompoundKey =
FirstLetter = student.LastName[0],
} into studentGroup
orderby studentGroup.Key.FirstLetter
select studentGroup;
Console.WriteLine($"\t{item.FirstName} {item.LastName}");
/* Output:
Terry Adams
Fadi Fakhouri
Hanying Feng
Cesar Garcia
Hugo Garcia
Debra Garcia
Sven Mortensen
Claire O'Donnell
Svetlana Omelchenko
Lance Tucker
Michael Tucker
Eugene Zabokritski
*/
Vea también
GroupBy
IGrouping<TKey,TElement>
Language-Integrated Query (LINQ)
group (cláusula)
Tipos anónimos
Realizar una subconsulta en una operación de agrupación
Crear un grupo anidado
Agrupar datos
Crear un grupo anidado
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Ejemplo
7 Nota
En el ejemplo de este tema se usa la clase y Student la lista students del código de
ejemplo incluido en Consulta de una colección de objetos.
C#
var nestedGroupsQuery =
from newGroup2 in (
from student in newGroup1
Console.WriteLine($"\t\t{innerGroupElement.LastName}
{innerGroupElement.FirstName}");
/* Output:
Adams Terry
Garcia Hugo
Omelchenko Svetlana
Fakhouri Fadi
Names that begin with: Garcia
Garcia Debra
Tucker Lance
Feng Hanying
Mortensen Sven
Tucker Michael
Garcia Cesar
O'Donnell Claire
Zabokritski Eugene
*/
Tenga en cuenta que se necesitan tres bucles foreach anidados para recorrer en
iteración los elementos internos de un grupo anidado.
Vea también
Language-Integrated Query (LINQ)
Realizar una subconsulta en una
operación de agrupación
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
En este artículo se muestran dos maneras diferentes de crear una consulta que ordena
los datos de origen en grupos y, luego, realiza una subconsulta en cada grupo de forma
individual. La técnica básica de cada ejemplo consiste en agrupar los elementos de
origen usando una continuación denominada newGroup y después generar una nueva
subconsulta en newGroup . Esta subconsulta se ejecuta en cada uno de los nuevos grupos
creados por la consulta externa. Tenga en cuenta que en este ejemplo concreto el
resultado final no es un grupo, sino una secuencia plana de tipos anónimos.
Para obtener más información sobre cómo agrupar, consulte Cláusula group.
Ejemplo
7 Nota
En los ejemplos de este tema se usa la clase Student y la lista students del código
de ejemplo incluido en Consulta de una colección de objetos.
C#
var queryGroupMax =
select new
Level = studentGroup.Key,
HighestScore = (
select student2.ExamScores.Average()
).Max()
};
La consulta del fragmento de código anterior también se puede escribir con la sintaxis
de método. El siguiente fragmento de código tiene una consulta semánticamente
equivalente escrita con sintaxis de método.
C#
var queryGroupMax =
students
Level = studentGroup.Key,
});
Vea también
Language-Integrated Query (LINQ)
Agrupar resultados por claves contiguas
Artículo • 15/02/2023 • Tiempo de lectura: 6 minutos
Clave Value
A We
A think
A that
B Linq
C is
A really
B cool
B !
C#
) =>
source.ChunkBy(keySelector, EqualityComparer<TKey>.Default);
IEqualityComparer<TKey> comparer
if (!enumerator.MoveNext())
yield break;
while (true)
// Get the key for the current Chunk. The source iterator will
churn through
// At this point the Chunk only has the first element in its
source sequence. The remaining elements will be
// returned only when the client code foreach's over this chunk.
See Chunk.GetEnumerator for more info.
// Check to see whether (a) the chunk has made a copy of all its
source elements or
// (b) the iterator has reached the end of the source sequence.
If the caller uses an inner
// foreach loop to iterate the chunk items, and that loop ran to
completion,
if (current.CopyAllChunkElements() == noMoreSourceElements)
yield break;
// has a key and a list of ChunkItem objects, which are copies of the
elements in the source sequence.
class ChunkItem
Value = value;
// ChunkItem is added.
// Flag to indicate the source iterator has reached the end of the
source sequence.
Key = key;
this.enumerator = enumerator;
this.predicate = predicate;
// The end and beginning are the same until the list contains > 1
elements.
tail = head;
// Indicates that all chunk elements have been copied to the list of
ChunkItems,
// key of the next element does not match the current chunk's key, or
there are no more elements in the source.
isLastSourceElement = !enumerator.MoveNext();
// If we are (a) at the end of the source, or (b) at the end of the
current chunk
// then null out the enumerator and predicate for reuse with the
next chunk.
if (isLastSourceElement || !predicate(enumerator.Current))
enumerator = null;
predicate = null;
else
tail = tail.Next!;
// Called after the end of the last chunk was reached. It first checks
whether
while (true)
lock (m_Lock)
if (DoneCopyingChunk)
// If isLastSourceElement is false,
// to continue iterating.
return isLastSourceElement;
else
CopyNextChunkElement();
// Invoked by the inner foreach loop. This method stays just one step
ahead
// of the client requests. It adds the next element of the chunk only
after
lock (m_Lock)
if (current == tail)
CopyNextChunkElement();
current = current.Next;
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
new("A","We"),
new("A","think"),
new("A","that"),
new("B","LINQ"),
new("C","is"),
new ("A","really"),
new("B","cool"),
new("B","!")
};
// on different threads.
Console.WriteLine($"\t{inner.Value}");
Vea también
Language-Integrated Query (LINQ)
Especificación de filtros con predicado
de forma dinámica en tiempo de
ejecución
Artículo • 09/03/2023 • Tiempo de lectura: 2 minutos
En algunos casos, no se conoce cuántos predicados hay que aplicar a los elementos de
origen de la cláusula where hasta el tiempo de ejecución. Una forma de especificar
dinámicamente varios filtros con predicado es usar el método Contains, como se
muestra en el ejemplo siguiente. La consulta devolverá resultados distintos en función
del valor de id al ejecutarse la consulta.
C#
var queryNames =
where ids.Contains(student.ID)
select new
student.LastName,
student.ID
};
Console.WriteLine($"{name.LastName}: {name.ID}");
/* Output:
Garcia: 114
O'Donnell: 112
Omelchenko: 111
*/
Console.WriteLine($"{name.LastName}: {name.ID}");
/* Output:
Adams: 120
Feng: 117
Garcia: 115
Tucker: 122
*/
C#
IEnumerable<Student> studentQuery;
if (oddYear)
studentQuery =
select student;
else
studentQuery =
select student;
Console.WriteLine($"{name.LastName}: {name.ID}");
FilterByYearType(true);
/* Output:
Fakhouri: 116
Feng: 117
Garcia: 115
Mortensen: 113
Tucker: 119
Tucker: 122
*/
FilterByYearType(false);
/* Output:
Adams: 120
Garcia: 114
Garcia: 118
O'Donnell: 112
Omelchenko: 111
Zabokritski: 121
*/
Vea también
Language-Integrated Query (LINQ)
where (cláusula)
Consultas basadas en el estado del entorno de ejecución
Realizar combinaciones internas
Artículo • 15/02/2023 • Tiempo de lectura: 7 minutos
7 Nota
C#
C#
new("Boots", terry),
new("Whiskers", charlotte),
new("Daisy", magnus),
};
// is an anonymous type containing both the person's name and their pet's
name.
var query =
select new
OwnerName = person.FirstName,
PetName = pet.Name
};
Console.WriteLine($"\"{ownerAndPet.PetName}\" is owned by
{ownerAndPet.OwnerName}");
/* Output:
*/
En el ejemplo siguiente se usa una lista de objetos Employee y una lista de objetos
Student para determinar qué empleados son también alumnos. Estos dos tipos tienen
una propiedad FirstName y una propiedad LastName de tipo String. Las funciones que
crean las claves de combinación de los elementos de cada lista devuelven un tipo
anónimo formado por las propiedades FirstName y LastName de cada elemento. La
operación de combinación compara la igualdad de estas claves compuestas y devuelve
pares de objetos de cada lista en los que el nombre y el apellido coinciden.
C#
};
};
// Join the two data sources based on a composite key consisting of first
and last name,
var query =
employee.FirstName,
employee.LastName
} equals new
student.FirstName,
student.LastName
Console.WriteLine(name);
/* Output:
Terry Adams
Vernette Price
*/
En el ejemplo siguiente se crean tres colecciones: una lista de objetos Person , una lista
de objetos Cat y una lista de objetos Dog .
La primera cláusula join de C# empareja personas y gatos según un objeto Person que
coincida con Cat.Owner . Devuelve una secuencia de tipos anónimos que contienen el
objeto Person y Cat.Name .
C#
Person magnus = new(FirstName: "Magnus", LastName: "Hedlund");
new("Boots", terry),
new("Whiskers", charlotte),
new("Daisy", magnus),
};
new("Duke", magnus),
new("Denim", terry),
new("Wiley", charlotte),
new("Snoopy", rui),
new("Snickers", arlene),
};
// The first join matches Person and Cat.Owner from the list of people and
// cats, based on a common Person. The second join matches dogs whose names
start
// with the same letter as the cats that have the same owner.
var query =
Owner = person,
Letter = cat.Name.Substring(0, 1)
} equals new
dog.Owner,
Letter = dog.Name.Substring(0, 1)
select new
CatName = cat.Name,
DogName = dog.Name
};
Console.WriteLine(
);
/* Output:
The cat "Daisy" shares a house, and the first letter of their name,
with "Duke".
The cat "Whiskers" shares a house, and the first letter of their name,
with "Wiley".
*/
En query1 , la lista de objetos Person forma una combinación agrupada con la lista de
objetos Pet según el Person que coincide con la propiedad Pet.Owner . La combinación
agrupada crea una colección de grupos intermedios, donde cada grupo consta de un
objeto Person y una secuencia de objetos Pet coincidentes.
C#
new("Boots", terry),
new("Whiskers", charlotte),
new("Daisy", magnus),
};
var query1 =
from subpet in gj
select new
OwnerName = person.FirstName,
PetName = subpet.Name
};
Console.WriteLine($"{v.OwnerName} - {v.PetName}");
var query2 =
select new
OwnerName = person.FirstName,
PetName = pet.Name
};
Console.WriteLine();
Console.WriteLine($"{v.OwnerName} - {v.PetName}");
/* Output:
Terry - Barley
Terry - Boots
Charlotte - Whiskers
Magnus - Daisy
Terry - Barley
Terry - Boots
Charlotte - Whiskers
*/
Consulte también
Join
GroupJoin
Realizar combinaciones agrupadas
Realizar operaciones de combinación externa izquierda
Tipos anónimos (Guía de programación de C#).
Realizar combinaciones agrupadas
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
Por ejemplo, una clase o una tabla de base de datos relacional denominada Student
podría contener dos campos: Id y Name . Una segunda clase o tabla de base de datos
relacional denominada Course podría contener dos campos: StudentId y CourseTitle .
Una combinación agrupada de estos dos orígenes de datos, basada en la combinación
de Student.Id y Course.StudentId , agruparía cada Student con una colección de
objetos Course (que podrían estar vacíos).
7 Nota
2 Advertencia
En los ejemplos de este tema se usan las clases de datos Person y Pet de
Realización de combinaciones internas.
C#
new("Boots", terry),
new("Whiskers", charlotte),
new("Daisy", magnus),
};
var query =
select new
OwnerName = person.FirstName,
Pets = gj
};
Console.WriteLine($"{v.OwnerName}:");
Console.WriteLine($" {pet.Name}");
/* Output:
Magnus:
Daisy
Terry:
Barley
Boots
Blue Moon
Charlotte:
Whiskers
Arlene:
*/
C#
// using System.Xml.Linq;
new("Boots", terry),
new("Whiskers", charlotte),
new("Daisy", magnus),
};
from subpet in gj
);
Console.WriteLine(ownersAndPets);
/* Output:
<PetOwners>
<Pet>Daisy</Pet>
</Person>
<Pet>Barley</Pet>
<Pet>Boots</Pet>
<Pet>Blue Moon</Pet>
</Person>
<Pet>Whiskers</Pet>
</Person>
</PetOwners>
*/
Vea también
Join
GroupJoin
Realizar combinaciones internas
Realizar operaciones de combinación externa izquierda
Tipos anónimos (Guía de programación de C#).
Realizar operaciones de combinación
externa izquierda
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
7 Nota
En el ejemplo de este tema se usan las clases de datos Pet y Person de Realizar
combinaciones internas.
Ejemplo
En el ejemplo siguiente se muestra cómo usar el método DefaultIfEmpty en los
resultados de una combinación agrupada para realizar una combinación externa
izquierda.
El primer paso para generar una combinación externa izquierda de dos colecciones
consiste en realizar una combinación interna usando una combinación agrupada.
(Consulte Realizar combinaciones internas para obtener una explicación de este
proceso). En este ejemplo, la lista de objetos Person está combinada internamente con
la lista de objetos Pet en función de un objeto Person que coincide con Pet.Owner .
7 Nota
El valor predeterminado para un tipo de referencia es null ; por consiguiente, el
ejemplo busca una referencia NULL antes de tener acceso a cada elemento de cada
colección de Pet .
C#
var query =
select new
person.FirstName,
};
Console.WriteLine($"{v.FirstName + ":",-15}{v.PetName}");
//
// Magnus: Daisy
// Terry: Barley
// Terry: Boots
// Charlotte: Whiskers
// Arlene:
Vea también
Join
GroupJoin
Realizar combinaciones internas
Realizar combinaciones agrupadas
Tipos anónimos (Guía de programación de C#).
Ordenar los resultados de una cláusula
join
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
7 Nota
C#
Ejemplo
Esta consulta crea una combinación agrupada y luego ordena los grupos según el
elemento categoría, que todavía está en el ámbito. Dentro del inicializador de tipo
anónimo, una subconsulta ordena todos los elementos coincidentes de la secuencia de
productos.
C#
new("Condiments", 002),
new("Vegetables", 003),
new("Grains", 004),
new("Fruit", 005)
};
new("Tea", 001),
new("Mustard", 002),
new("Pickles", 002),
new("Carrots", 003),
new("Peaches", 005),
new("Melons", 005),
};
var groupJoinQuery2 =
orderby category.Name
select new
Category = category.Name,
Products =
orderby prod2.Name
select prod2
};
Console.WriteLine(productGroup.Category);
/* Output:
Beverages
Cola 1
Tea 1
Condiments
Mustard 2
Pickles 2
Fruit
Melons 5
Peaches 5
Grains
Vegetables
Bok Choy 3
Carrots 3
*/
Vea también
Language-Integrated Query (LINQ)
orderby (cláusula)
join (cláusula)
Realizar una unión usando claves
compuestas
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Ejemplo
En el ejemplo siguiente se muestra cómo usar una clave compuesta para combinar
datos de tres tablas:
C#
from p in db.Products
join d in db.OrderDetails
from d in details
C#
Cuando tenga que introducir una variable de rango temporal para la secuencia de
la derecha (interior) antes de la operación de combinación.
Para realizar combinaciones que no son de igualdad, puede usar varias cláusulas from
para presentar cada origen de datos de forma independiente. Después, aplique una
expresión de predicado de una cláusula where a la variable de rango para cada origen.
La expresión también puede adoptar la forma de una llamada de método.
7 Nota
Combinación cruzada
7 Nota
C#
new("Condiments", 002),
new("Vegetables", 003)
};
new("Mustard", 002),
new("Pickles", 002),
new("Carrots", 003),
new("Peaches", 005),
new("Melons", 005),
new("Mackerel", 012)
};
var crossJoinQuery =
from c in categories
from p in products
select new
c.ID,
p.Name
};
Console.WriteLine($"{v.ID,-5}{v.Name}");
/* Output:
1 Tea
1 Mustard
1 Pickles
1 Carrots
1 Bok Choy
1 Peaches
1 Melons
1 Ice Cream
1 Mackerel
2 Tea
2 Mustard
2 Pickles
2 Carrots
2 Bok Choy
2 Peaches
2 Melons
2 Ice Cream
2 Mackerel
3 Tea
3 Mustard
3 Pickles
3 Carrots
3 Bok Choy
3 Peaches
3 Melons
3 Ice Cream
3 Mackerel
*/
Combinaciones de desigualdad
Esta consulta genera una secuencia de todos los productos cuyo Id. de categoría
aparece en la lista de categorías en el lado izquierdo. Observe el uso de la cláusula let
y el método Contains para crear una matriz temporal. También se puede crear la matriz
antes de la consulta y eliminar la primera cláusula from .
C#
var nonEquijoinQuery =
from p in products
let catIds =
from c in categories
select c.ID
select new
Product = p.Name,
p.CategoryID
};
Console.WriteLine("Non-equijoin query:");
Console.WriteLine($"{v.CategoryID,-5}{v.Product}");
/* Output:
Non-equijoin query:
1 Tea
2 Mustard
2 Pickles
3 Carrots
3 Bok Choy
*/
C#
// You could use var instead of an explicit type for the query.
IEnumerable<Student> queryNamesScores =
let x = name.Split(',')
let s = score.Split(',')
FirstName: x[0],
LastName: x[1],
StudentID: int.Parse(x[2]),
ExamScores: (
select int.Parse(scoreAsText)
).ToList()
);
/* Output:
*/
Vea también
Language-Integrated Query (LINQ)
join (cláusula)
Ordenar los resultados de una cláusula join
Controlar valores nulos en expresiones
de consulta
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
En este ejemplo se muestra cómo controlar los posibles valores nulos en colecciones de
origen. Una colección de objetos como IEnumerable<T> puede contener elementos
cuyo valor es NULL. Si una colección de origen es null o contiene un elemento cuyo
valor es null , y la consulta no controla los valores null , se iniciará un elemento
NullReferenceException cuando se ejecute la consulta.
Se pueden codificar de forma defensiva para evitar una excepción de referencia nula, tal
y como se muestra en el ejemplo siguiente:
C#
var query1 =
from c in categories
where c != null
select new
Category = c.Name,
Name = p.Name
};
En el ejemplo anterior, la cláusula where filtra todos los elementos nulos de la secuencia
de categorías. Esta técnica es independiente de la comprobación de null en la cláusula
join. La expresión condicional con NULL de este ejemplo funciona porque
Products.CategoryID es de tipo int? , que es una abreviatura de Nullable<int> .
En una cláusula join, si solo una de las claves de comparación es de un tipo que acepta
valores NULL, puede convertir la otra en un tipo que acepta valores NULL en la
expresión de consulta. En el ejemplo siguiente, suponga que EmployeeID es una
columna que contiene valores de tipo int? :
C#
var query =
from o in db.Orders
join e in db.Employees
En cada uno de los ejemplos, se usa la palabra clave de consulta equals . C# 9 agrega
una coincidencia de patrones, que incluye patrones para is null y is not null . Estos
patrones no se recomiendan en las consultas LINQ porque es posible que los
proveedores de consultas no interpreten correctamente la sintaxis nueva de C#. Un
proveedor de consultas es una biblioteca que traduce expresiones de consulta de C# a
un formato de datos nativo, como Entity Framework Core. Los proveedores de consultas
implementan la interfaz System.Linq.IQueryProvider para crear orígenes de datos que
implementan la interfaz System.Linq.IQueryable<T>.
Vea también
Nullable<T>
Language-Integrated Query (LINQ)
Tipos de valores que aceptan valores NULL
Controlar excepciones en expresiones
de consulta
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
En el último ejemplo se muestra cómo controlar los casos en los que se debe producir
una excepción durante la ejecución de una consulta.
Ejemplo 1
En el ejemplo siguiente se muestra cómo mover código de control de excepciones fuera
de una expresión de consulta. Esto solo es posible cuando el método no depende de
ninguna variable local de la consulta.
C#
try
dataSource = GetData();
catch (InvalidOperationException)
{
Console.WriteLine("Invalid operation");
var query =
from i in dataSource
select i * i;
Console.WriteLine(i.ToString());
Ejemplo 2
En algunos casos, la mejor respuesta a una excepción que se produce dentro de una
consulta podría ser detener la ejecución de la consulta inmediatamente. En el ejemplo
siguiente se muestra cómo controlar las excepciones que pueden producirse desde el
cuerpo de una consulta. Supongamos que SomeMethodThatMightThrow puede producir
una excepción que requiere que se detenga la ejecución de la consulta.
Tenga en cuenta que el bloque try encierra el bucle foreach y no la propia consulta.
Esto es porque el bucle foreach es el punto en el que se ejecuta realmente la consulta.
Para más información, vea Introducción a las consultas LINQ.
C#
s[4] == 'C' ?
@"C:\newFolder\" + s;
// Data source.
var exceptionDemoQuery =
let n = SomeMethodThatMightThrow(file)
select n;
// The runtime exception will only be thrown when the query is executed.
try
Console.WriteLine($"Processing {item}");
catch (InvalidOperationException e)
Console.WriteLine(e.Message);
}
/* Output:
Processing C:\newFolder\fileA.txt
Processing C:\newFolder\fileB.txt
*/
Vea también
Language-Integrated Query (LINQ)
Reduce memory allocations using new
C# features
Artículo • 14/02/2023 • Tiempo de lectura: 8 minutos
) Importante
The techniques described in this section improve performance when applied to hot
paths in your code. Hot paths are those sections of your codebase that are
executed often and repeatedly in normal operations. Applying these techniques to
code that isn't often executed will have minimal impact. Before making any
changes to improve performance, it's critical to measure a baseline. Then, analyze
that baseline to determine where memory bottlenecks occur. You can learn about
many cross platform tools to measure your application's performance in the section
on Diagnostics and instrumentation. You can practice a profiling session in the
tutorial to Measure memory usage in the Visual Studio documentation.
Once you've measured memory usage and have determined that you can reduce
allocations, use the techniques in this section to reduce allocations. After each
successive change, measure memory usage again. Make sure each change has a
positive impact on the memory usage in your application.
Performance work in .NET often means removing allocations from your code. Every
block of memory you allocate must eventually be freed. Fewer allocations reduce time
spent in garbage collection. It allows for more predictable execution time by removing
garbage collections from specific code paths.
A common tactic to reduce allocations is to change critical data structures from class
types to struct types. This change impacts the semantics of using those types.
Parameters and returns are now passed by value instead of by reference. The cost of
copying a value is negligible if the types are small, three words or less. It's measurable
and can have real performance impact for larger types. To combat the effect of copying,
developers can pass these types by ref to get back the intended semantics.
The C# ref features give you the ability to express the desired semantics for struct
types without negatively impacting their overall usability. Prior to these enhancements,
developers needed to resort to unsafe constructs with pointers and raw memory to
achieve the same performance impact. The compiler generates verifiably safe code for
the new ref related features. Verifiably safe code means the compiler detects possible
buffer overruns or accessing unallocated or freed memory. The compiler detects and
prevents some errors.
In C#, parameters to methods are passed by value, and return values are return by value.
The value of the argument is passed to the method. The value of the return argument is
the return value.
The ref , in , or out modifier indicates that parameter is passed by reference. The
reference to the storage location is passed to the method. Adding ref to the method
signature means the return value is returned by reference. The reference to the storage
location is the return value.
You can also use ref assignment to have a variable refer to another variable. A typical
assignment copies the value of the right hand side to the variable on the left hand side
of the assignment. A ref assignment copies the memory location of the variable on the
right hand side to the variable on the left hand side. The ref now refers to the original
variable:
C#
Console.WriteLine(location); // output: 42
Console.WriteLine(anInteger); // output: 19
When you assign a variable, you change its value. When you ref assign a variable, you
change what it refers to.
You can work directly with the storage for values using ref variables, pass by reference,
and ref assignment. Scope rules enforced by the compiler ensure safety when working
directly with storage.
C#
return ref index; // Error: index's ref safe to escape scope is the body
of CantEscape
The compiler reports an error because you can't return a reference to a local variable
from a method. The caller can't access the storage being referred to. The ref safe to
escape scope defines the scope in which a ref expression is safe to access or modify.
The following table lists the ref safe to escape scopes for variable types. ref fields can't
be declared in a class or a non-ref struct , so those those rows aren't in the table:
A variable can be ref returned if its ref safe to escape scope is the calling method. If its
ref safe to escape scope is the current method or a block, ref return is disallowed. The
following snippet shows two examples. A member field can be accessed from the scope
calling a method, so a class or struct field's ref safe to escape scope is the calling method.
The ref safe to escape scope for a parameter with the ref , or in modifiers is the entire
method. Both can be ref returned from a member method:
C#
else
7 Nota
The compiler ensures that a reference can't escape its ref safe to escape scope. You can
use ref parameters, ref return and ref local variables safely because the compiler
detects if you've accidentally written code where a ref expression could be accessed
when its storage isn't valid.
Informally, the safe to escape scope for a ref struct is the scope where all of its ref
fields can be accessed. In other words, it's the intersection of the ref safe to escape
scopes of all its ref fields. The following method returns a ReadOnlySpan<char> to a
member field, so its safe to escape scope is the method:
C#
private string longMessage = "This is a long message";
return span;
In contrast, the following code emits an error because the ref field member of the
Span<int> refers to the stack allocated array of integers. It can't escape the method:
C#
int length = 3;
numbers[i] = i;
struct types. Spans contain a ref field . Therefore instances of a span can't leave its
safe to escape scope. The safe to escape scope of a ref struct is the ref safe to escape
scope of its ref field . The implementation of Memory<T> and ReadOnlyMemory<T>
remove this restriction. You use these types to directly access memory buffers.
Avoid allocations: When you change a type from a class to a struct , you change
how it's stored. Local variables are stored on the stack. Members are stored inline
when the container object is allocated. This change means fewer allocations and
that decreases the work the garbage collector does. It may also decrease memory
pressure so the garbage collector runs less often.
Preserve reference semantics: Changing a type from a class to a struct changes
the semantics of passing a variable to a method. Code that modified the state of
its parameters needs modification. Now that the parameter is a struct , the
method is modifying a copy of the original object. You can restore the original
semantics by passing that parameter as a ref parameter. After that change, the
method modifies the original struct again.
Avoid copying data: Copying larger struct types can impact performance in some
code paths. You can also add the ref modifier to pass larger data structures to
methods by reference instead of by value.
Restrict modifications: When a struct type is passed by reference, the called
method could modify the state of the struct. You can replace the ref modifier with
the in modifier to indicate that the argument can't be modified. You can also
create readonly struct types or struct types with readonly members to provide
more control over what members of a struct can be modified.
Directly manipulate memory: Some algorithms are most efficient when treating
data structures as a block of memory containing a sequence of elements. The Span
and Memory types provide safe access to blocks of memory.
None of these techniques require unsafe code. Used wisely, you can get performance
characteristics from safe code that was previously only possible by using unsafe
techniques. You can try the techniques yourself in the tutorial on reducing memory
allocations
Expression Trees
Artículo • 09/03/2023 • Tiempo de lectura: 4 minutos
Expression trees represent code in a tree-like data structure, where each node is an
expression, for example, a method call or a binary operation such as x < y .
If you have used LINQ, you have experience with a rich library where the Func types are
part of the API set. (If you aren't familiar with LINQ, you probably want to read the LINQ
tutorial and the article about lambda expressions before this one.) Expression Trees
provide richer interaction with the arguments that are functions.
You write function arguments, typically using Lambda Expressions, when you create
LINQ queries. In a typical LINQ query, those function arguments are transformed into a
delegate the compiler creates.
You've likely already written code that uses Expression trees. Entity Framework's LINQ
APIs accept Expression trees as the arguments for the LINQ Query Expression Pattern.
That enables Entity Framework to translate the query you wrote in C# into SQL that
executes in the database engine. Another example is Moq , which is a popular
mocking framework for .NET.
When you want to have a richer interaction, you need to use Expression Trees. Expression
Trees represent code as a structure that you examine, modify, or execute. These tools
give you the power to manipulate code during run time. You write code that examines
running algorithms, or injects new capabilities. In more advanced scenarios, you modify
running algorithms and even translate C# expressions into another form for execution in
another environment.
You compile and run code represented by expression trees. Building and running
expression trees enables dynamic modification of executable code, the execution of
LINQ queries in various databases, and the creation of dynamic queries. For more
information about expression trees in LINQ, see How to use expression trees to build
dynamic queries.
Expression trees are also used in the dynamic language runtime (DLR) to provide
interoperability between dynamic languages and .NET and to enable compiler writers to
emit expression trees instead of Microsoft intermediate language (MSIL). For more
information about the DLR, see Dynamic Language Runtime Overview.
You can have the C# or Visual Basic compiler create an expression tree for you based on
an anonymous lambda expression, or you can create expression trees manually by using
the System.Linq.Expressions namespace.
When a lambda expression is assigned to a variable of type Expression<TDelegate>, the
compiler emits code to build an expression tree that represents the lambda expression.
The C# compiler generates expression trees only from expression lambdas (or single-
line lambdas). It can't parse statement lambdas (or multi-line lambdas). For more
information about lambda expressions in C#, see Lambda Expressions.
The following code examples demonstrate how to have the C# compiler create an
expression tree that represents the lambda expression num => num < 5 .
C#
You create expression trees in your code. You build the tree by creating each node and
attaching the nodes into a tree structure. You learn how to create expressions in the
article on building expression trees.
Expression trees are immutable. If you want to modify an expression tree, you must
construct a new expression tree by copying the existing one and replacing nodes in it.
You use an expression tree visitor to traverse the existing expression tree. For more
information, see the article on translating expression trees.
Once you build an expression tree, you execute the code represented by the expression
tree.
Limitations
There are some newer C# language elements that don't translate well into expression
trees. Expression trees can't contain await expressions, or async lambda expressions.
Many of the features added in C# 6 and later don't appear exactly as written in
expression trees. Instead, newer features are exposed in expression trees in the
equivalent, earlier syntax, where possible. Other constructs aren't available. It means that
code that interprets expression trees works the same when new language features are
introduced. However, the expression trees Even with these limitations, expression trees
do enable you to create dynamic algorithms that rely on interpreting and modifying
code that is represented as a data structure. It enables rich libraries such as Entity
Framework to accomplish what they do.
Expression trees won't support new expression node types. It would be a breaking
change for all libraries interpreting expression trees to introduce new node types. The
following list includes most C# language elements that can't be used:
Conditional methods that have been removed
base access
Method group expressions, including address-of (&) a method group, and
anonymous method expressions
References to local functions
Statements, including assignment ( = ) and statement bodied expressions
Partial methods with only a defining declaration
Unsafe pointer operations
dynamic operations
Coalescing operators with null or default literal left side, null coalescing
assignment, and the null propagating operator (?.)
Multi-dimensional array initializers, indexed properties, and dictionary initializers
throw expressions
Accessing static virtual or abstract interface members
Lambda expressions that have attributes
Interpolated strings
UTF-8 string conversions or UTF-8 string literals
Method invocations using variable arguments, named arguments or optional
arguments
Expressions using System.Index or System.Range, index "from end" (^) operator or
range expressions (..)
async lambda expressions or await expressions, including await foreach and await
using
Tuple literals, tuple conversions, tuple == or !=, or with expressions
Discards (_), deconstructing assignment, pattern matching is operator or the
pattern matching switch expression
COM call with ref omitted on the arguments
ref, in or out parameters, ref return values, out arguments, or any values of ref
struct type
Árboles de expresión: datos que definen
el código
Artículo • 13/03/2023 • Tiempo de lectura: 4 minutos
Los árboles de expresiones son estructuras de datos que definen código. Los árboles de
expresión se basan en las mismas estructuras que usa un compilador para analizar el
código y generar el resultado compilado. A medida que lea este artículo, observará
cierta similitud entre los árboles de expresiones y los tipos usados en las API de Roslyn
para compilar analizadores y correcciones de código . (Los analizadores y las
correcciones de código son paquetes de NuGet que realizan un análisis estático en
código y sugieren posibles correcciones para un desarrollador). Los conceptos son
similares y el resultado final es una estructura de datos que permite examinar el código
fuente de forma significativa. En cambio, los árboles de expresiones se basan en un
conjunto de clases y API diferente a las API de Roslyn. Aquí tiene una línea de código:
C#
var sum = 1 + 2;
Si analiza el código anterior como un árbol de expresión, el árbol contiene varios nodos.
El nodo más externo es una instrucción de declaración de variable con asignación ( var
sum = 1 + 2; ). Ese nodo exterior contiene varios nodos secundarios: una declaración de
variable, un operador de asignación y una expresión que representa el lado derecho del
signo igual. Esa expresión se subdivide aún más en expresiones que representan la
operación de suma, y los operandos izquierdo y derecho de la suma.
Vamos a profundizar un poco más en las expresiones que constituyen el lado derecho
del signo igual. La expresión es 1 + 2 , una expresión binaria. Concretamente, es una
expresión binaria de suma. Una expresión binaria de suma tiene dos elementos
secundarios, que representan los nodos izquierdo y derecho de la expresión de suma.
En este caso, ambos nodos son expresiones constantes: el operando izquierdo es el
valor 1 y el operando derecho es el valor 2 .
El árbol anterior puede parecer complicado, pero es muy versátil. Siguiendo el mismo
proceso, descompone expresiones mucho más complicadas. Tomemos esta expresión
como ejemplo:
C#
currentState.createInterimResult(), currentState.createSecondValue(1,
2),
decisionServer.considerFinalOptions("hello")) +
A pesar de esta aparente complejidad, la expresión anterior crea una estructura de árbol
por la que se navega con tanta facilidad como en el primer ejemplo. Siga recorriendo
los nodos secundarios para buscar nodos hoja en la expresión. Los nodos primarios
tienen referencias a sus elementos secundarios y cada nodo tiene una propiedad que
describe de qué tipo es.
La estructura de los árboles de expresiones es muy coherente. Una vez que conozca los
aspectos básicos, puede entender incluso el código más complejo cuando esté
representado como un árbol de expresión. La elegancia de la estructura de datos explica
cómo el compilador de C# analiza los programas de C# más complejos y crea resultados
correctos a partir de código fuente complicado.
Una vez que esté familiarizado con la estructura de los árboles de expresiones, verá que
los conocimientos que ha adquirido le permiten trabajar rápidamente con muchos
escenarios más avanzados. Los árboles de expresiones ofrecen posibilidades increíbles.
Las API de los árboles de expresiones permiten crear árboles que representan casi
cualquier construcción de código válida. En cambio, para que todo resulte lo más
sencillo posible, algunas expresiones de C# no se pueden crear en un árbol de
expresión. Un ejemplo son las expresiones asincrónicas (mediante las palabras clave
async y await ). Si necesita algoritmos sincrónicos, tendría que manipular los objetos
Hay una amplia lista de clases en el entorno de ejecución de .NET que funcionan con
árboles de expresiones. En System.Linq.Expressions puede ver la lista completa. En lugar
de enumerar la lista completa, vamos a explicar cómo se han diseñado las clases del
entorno de ejecución.
Por ejemplo, este código imprime el nombre de una variable para una expresión de
acceso a la variable. El siguiente código muestra la práctica de comprobar el tipo de
nodo, convertirlo en una expresión de acceso a la variable y después comprobar las
propiedades del tipo de expresión específico:
C#
Expression<Func<int, int>> addFive = (num) => num + 5;
if (addFive.NodeType == ExpressionType.Lambda)
Console.WriteLine(parameter.Name);
Console.WriteLine(parameter.Type);
C#
En este sencillo ejemplo puede ver que hay muchos tipos implicados a la hora de crear
árboles de expresiones y trabajar con ellos. Esta complejidad resulta necesaria para
proporcionar las capacidades del vocabulario variado que ofrece el lenguaje C#.
Encontrará más información a medida que observe cada una de esas tres áreas. Siempre
encontrará lo que necesita empezando con uno de esos tres pasos.
Ejecución de árboles de expresión
Artículo • 13/03/2023 • Tiempo de lectura: 7 minutos
7 Nota
Si un árbol de expresión no representa una expresión lambda, puede crear una nueva
expresión lambda que tenga el árbol de expresión original como su cuerpo llamando al
método Lambda<TDelegate>(Expression, IEnumerable<ParameterExpression>). Luego
puede ejecutar la expresión lambda tal y como se ha descrito anteriormente en esta
sección.
En la mayoría de los casos, existe una asignación simple entre una expresión y su
delegado correspondiente. Por ejemplo, un árbol de expresión que se representa por
Expression<Func<int>> se convertiría a un delegado del tipo Func<int> . Para una
expresión lambda con cualquier tipo de valor devuelto y lista de argumentos, existe un
tipo de delegado que es el tipo de destino para el código ejecutable representado por
esa expresión lambda.
) Importante
y versiones posteriores.
C#
Console.WriteLine(answer);
C#
Console.WriteLine(result(4));
// Prints True.
Console.WriteLine(expr.Compile()(4));
C#
BinaryExpression be = Expression.Power(Expression.Constant(2d),
Expression.Constant(3d));
Expression<Func<double>> le = Expression.Lambda<Func<double>>(be);
Console.WriteLine(result);
// 8
Ejecución y duraciones
El código se ejecuta mediante la invocación del delegado que se crea al llamar a
LambdaExpression.Compile() . El código anterior, add.Compile() , devuelve un delegado.
U Precaución
Advertencias
Compilar una expresión lambda en un delegado e invocar ese delegado es una de las
operaciones más simples que se pueden realizar con un árbol de expresión. Pero incluso
con esta sencilla operación, hay advertencias que debe conocer.
Las expresiones lambda crean clausuras sobre las variables locales a las que se hace
referencia en la expresión. Debe garantizar que las variables que formarían parte del
delegado se pueden usar en la ubicación desde la que se llama a Compile , y cuando se
ejecuta el delegado resultante. El compilador garantiza que las variables estén en el
ámbito. Pero si la expresión tiene acceso a una variable que implementa IDisposable , es
posible que el código deseche el objeto mientras se sigue manteniendo en el árbol de
expresión.
Por ejemplo, este código funciona bien porque int no implementa IDisposable :
C#
private static Func<int, int> CreateBoundFunc()
return rVal;
El delegado capturó una referencia a la variable local constant . Esa variable es accesible
en cualquier momento posterior, cuando se ejecuta la función devuelta por
CreateBoundFunc .
C#
get
if (!isDisposed)
return 5;
isDisposed = true;
C#
return rVal;
El delegado devuelto por este método se clausuró sobre el objeto constant , que se
eliminó. (Se eliminó porque se declaró en una instrucción using ).
Ahora, al ejecutar el delegado devuelto desde este método, se produce una excepción
ObjectDisposedException en el punto de ejecución.
Parece extraño tener un error en tiempo de ejecución que representa una construcción
de tiempo de compilación, pero es el mundo al que entra cuando trabaja con árboles de
expresión.
Hay numerosas permutaciones de este problema, por lo que resulta difícil ofrecer
instrucciones generales para evitarlo. Tenga cuidado al obtener acceso a las variables
locales al definir expresiones y al obtener acceso al estado en el objeto actual
(representado por this ) al crear un árbol de expresión devuelto por una API pública.
Resumen
Los árboles de expresión que representan expresiones lambda se pueden compilar para
crear un delegado que se puede ejecutar. Los árboles de expresión proporcionan un
mecanismo para ejecutar el código representado por un árbol de expresión.
C#
// using System.Linq.Expressions;
Ese diseño hace que la visita de todos los nodos de un árbol de expresión sea una
operación recursiva relativamente sencilla. La estrategia general es comenzar en el nodo
raíz y determinar qué tipo de nodo es.
Resultados
Ahora, vamos a escribir el código que examinará esta expresión y también algunas
propiedades importantes sobre este.
Expresión de suma
Comencemos con el ejemplo de adición de la instrucción de esta sección.
C#
7 Nota
No use var para declarar este árbol de expresión, porque el tipo natural del
delegado es Func<int> , no Expression<Func<int>> .
El nodo raíz es LambdaExpression . Para obtener el código que nos interesa en el lado
derecho del operador => , hay que buscar uno de los elementos secundarios de
LambdaExpression . Hace esto con todas las expresiones de esta sección. El nodo
Para examinar cada nodo de esta expresión, necesitaremos visitar recursivamente varios
nodos. Aquí se muestra una primera implementación sencilla:
C#
Expression<Func<int, int, int>> addition = (a, b) => a + b;
Console.WriteLine($"\tParameter Type:
{argumentExpression.Type.ToString()}, Name: {argumentExpression.Name}");
Resultados
C#
using System.Linq.Expressions;
namespace Visitors;
node.NodeType switch
};
// Lambda Visitor
argumentVisitor.Visit(prefix + "\t");
bodyVisitor.Visit(prefix + "\t");
left.Visit(prefix + "\t");
right.Visit(prefix + "\t");
// Parameter visitor:
this.node = node;
// Constant visitor:
encuentra un tipo de nodo nuevo. De este modo, sabe que se va a agregar un tipo de
expresión nuevo).
Resultados
Ahora que ha creado una implementación de visitante más general, puede visitar y
procesar muchos más tipos de expresiones diferentes.
C#
C#
Puede ver la separación en dos respuestas posibles para resaltar la más prometedora. La
primera representa las expresiones asociativas por la derecha. La segunda representa las
expresiones asociativas por la izquierda. La ventaja de los dos formatos es que el
formato escala a cualquier número arbitrario de expresiones de adición.
Si ejecuta esta expresión a través del visitante, verá este resultado y comprobará que la
expresión de adición simple es asociativa por la izquierda.
Para ejecutar este ejemplo, y ver el árbol de expresión completo, realiza un cambio en el
árbol de expresión de origen. Cuando el árbol de expresión contiene todas las
constantes, el árbol resultante simplemente contiene el valor constante de 10 . El
compilador realiza toda la adición y reduce la expresión a su forma más simple.
Simplemente con agregar una variable a la expresión es suficiente para ver el árbol
original:
C#
Cree un visitante para esta suma y ejecute el visitante para ver esta salida:
Resultados
Puede ejecutar cualquiera de los otros ejemplos a través del código de visitante y ver
qué árbol representa. Aquí se muestra un ejemplo de la expresión sum3 anterior (con un
parámetro adicional para evitar que el compilador calcule la constante):
C#
Resultados
Tenga en cuenta que los paréntesis no forman parte de la salida. No existen nodos en el
árbol de expresión que representen los paréntesis en la expresión de entrada. La
estructura del árbol de expresión contiene toda la información necesaria para comunicar
la precedencia.
C#
n == 0 ?
1 :
C#
node.NodeType switch
};
C#
this.node = node;
testVisitor.Visit(prefix + "\t");
trueVisitor.Visit(prefix + "\t");
falseVisitor.Visit(prefix + "\t");
this.node = node;
if (node.Object == null)
else
receiverVisitor.Visit(prefix + "\t");
argVisitor.Visit(prefix + "\t");
Resultados
This expression is a/an Lambda expression type
En primer lugar, los visitantes solo controlan constantes que son enteros. Los valores
constantes pueden ser cualquier otro tipo numérico, y el lenguaje de C# admite
conversiones y promociones entre esos tipos. Una versión más sólida de este código
reflejará todas esas capacidades.
Por último, la biblioteca usada en este artículo se ha creado con fines de demostración y
aprendizaje. No está optimizada. Aclara las estructuras y resalta las técnicas usadas para
visitar los nodos y analizar lo que hay ahí.
El compilador de C# creó todos los árboles de expresión que ha visto hasta ahora. Ha
creado una expresión lambda asignaba a una variable de tipo Expression<Func<T>> o de
algún tipo similar. En muchos escenarios, crea una expresión en memoria en tiempo de
ejecución.
Los árboles de expresión son inmutables. Inmutable significa que debe crear el árbol
desde las hojas hasta la raíz. Las API que usa para crear los árboles de expresión reflejan
este hecho: los métodos que usa para crear un nodo toman todos sus elementos
secundarios como argumentos. Veamos algunos ejemplos para mostrarle las técnicas.
Creación de nodos
Empezaremos con la expresión de adición con la que ha estado trabajando en estas
secciones:
C#
Para crear ese árbol de expresión, primero cree los nodos hoja. Los nodos hoja son
constantes. Use el método Constant para crear los nodos:
C#
C#
Una vez que haya creado la expresión de adición, se crea la expresión lambda:
C#
Para las expresiones como esta, puede combinar todas las llamadas en una sola
instrucción:
C#
Expression.Add(
Expression.Constant(1, typeof(int)),
Expression.Constant(2, typeof(int))
);
Creación de un árbol
En la sección anterior se muestran los conceptos básicos de la creación de un árbol de
expresión en memoria. Los árboles más complejos implican normalmente más tipos de
nodo y más nodos en el árbol. Vamos a analizar un ejemplo más y a mostrar dos tipos
de nodo que crea normalmente al crear árboles de expresión: los nodos de argumentos
y los nodos de llamada al método. Vamos a crear un árbol de expresión para crear esta
expresión:
C#
C#
C#
C#
C#
distance,
xParameter,
yParameter);
En este ejemplo más complejo, verá un par de técnicas más que necesitará a menudo
para crear árboles de expresión.
Primero, necesita crear los objetos que representan parámetros o variables locales antes
de usarlos. Una vez que haya creado esos objetos, puede usarlos en su árbol de
expresión siempre que los necesite.
Después, necesita usar un subconjunto de las API de reflexión para crear un objeto
System.Reflection.MethodInfo, de manera que pueda crear un árbol de expresión para
tener acceso a ese método. Debe limitarse al subconjunto de las API de reflexión que
están disponibles en la plataforma de .NET Core. De nuevo, estas técnicas se extienden a
otros árboles de expresión.
var res = 1;
while (n > 1)
res = res * n;
n--;
return res;
};
C#
Expression.Assign(result,
Expression.Multiply(result, nArgument)),
Expression.PostDecrementAssign(nArgument)
);
new[] { result },
initializeResult,
Expression.Loop(
Expression.IfThenElse(
Expression.GreaterThan(nArgument, Expression.Constant(1)),
block,
Expression.Break(label, result)
),
label
);
El código para crear el árbol de expresión para la función factorial es bastante más
largo, más complicado y está lleno de etiquetas, instrucciones Break y otros elementos
que le gustaría evitar en nuestras tareas de codificación diarias.
En esta sección, también actualiza el código del visitante para visitar cada nodo de este
árbol de expresión y escribir información sobre los nodos que se crean en este ejemplo.
Puede ver o descargar el código de ejemplo en el repositorio dotnet/docs de GitHub.
Pruébelo compilando y ejecutando los ejemplos.
C#
Expression.Lambda<Func<int, bool>>(
numLessThanFive,
C#
new[] { result },
Expression.Assign(result, Expression.Constant(1)),
// Adding a loop.
Expression.Loop(
Expression.IfThenElse(
// Condition: value > 1
Expression.GreaterThan(value, Expression.Constant(1)),
Expression.MultiplyAssign(result,
Expression.PostDecrementAssign(value)),
Expression.Break(label, result)
),
label
);
Console.WriteLine(factorial);
// Prints 120.
Para obtener más información, consulte Generating Dynamic Methods with Expression
Trees in Visual Studio 2010 (Generar métodos dinámicos con árboles de expresión en
Visual Studio 2010), que también se aplica a las últimas versiones de Visual Studio.
Traslado de árboles de expresión
Artículo • 13/03/2023 • Tiempo de lectura: 7 minutos
En este artículo, obtendrá información sobre cómo visitar cada nodo en un árbol de
expresión, mientras se crea una copia modificada de ese árbol de expresión. Trasladará
los árboles de expresión para comprender los algoritmos para poder trasladarlos a otro
entorno. Cambiará el algoritmo que se ha creado. Podría agregar el registro, interceptar
las llamadas de método y realizar un seguimiento de ellas, o con otros fines.
Aquí, una vez que se encuentre un nodo constante, se crea un nuevo nodo de
multiplicación cuyos elementos secundarios son la constante original y la constante 10 :
C#
if (original.NodeType == ExpressionType.Constant)
return Expression.Add(
ReplaceNodes(binaryExpression.Left),
ReplaceNodes(binaryExpression.Right));
return original;
Cree un nuevo árbol reemplazando el nodo original por el sustituto. Puede comprobar
los cambios mediante la compilación y ejecución del árbol reemplazado.
C#
Console.WriteLine(answer);
La creación de un árbol nuevo es una combinación de visitar los nodos del árbol
existente y crear nodos nuevos e insertarlos en el árbol. En el ejemplo anterior se
muestra la importancia de la inmutabilidad de los árboles de expresión. Observe que el
nuevo árbol creado anteriormente contiene una mezcla de los nodos recién creados y
los nodos del árbol existente. Los nodos se pueden usar en ambos árboles porque los
nodos del árbol existente no se pueden modificar. La reutilización de nodos da lugar a
importantes eficiencias de memoria. Los mismos nodos se pueden usar en un árbol o en
varios árboles de expresión. Dado que los nodos no se pueden modificar, se puede
volver a usar el mismo nodo siempre que sea necesario.
C#
// Aggregate, return constants, or the sum of the left and right operand.
exp.NodeType == ExpressionType.Constant ?
(int)((ConstantExpression)exp).Value :
aggregate(((BinaryExpression)exp).Left) +
aggregate(((BinaryExpression)exp).Right);
Console.WriteLine(theSum);
Aquí hay gran cantidad de código, pero los conceptos son accesibles. Este código visita
los elementos secundarios en una primera búsqueda de profundidad. Cuando encuentra
un nodo constante, el visitante devuelve el valor de la constante. Tras la visita a los dos
elementos secundarios por parte del visitante, dichos elementos han obtenido la suma
calculada para ese subárbol. El nodo de adición ahora puede calcular la suma. Una vez
que se visiten todos los nodos en el árbol de expresión, se habrá calculado la suma. Se
puede hacer el seguimiento de la ejecución ejecutando el ejemplo en el depurador y
realizando el seguimiento de la ejecución.
C#
if (exp.NodeType == ExpressionType.Constant)
return value;
else
return 0;
return sum;
Resultados
10
Found Constant: 1
Left is: 1
Found Constant: 2
Right is: 2
Computed sum: 3
Left is: 3
Found Constant: 3
Left is: 3
Found Constant: 4
Right is: 4
Computed sum: 7
Right is: 7
Computed sum: 10
10
Realice el seguimiento del resultado y siga el código anterior. Debería poder averiguar
cómo el código visita cada nodo y calcula la suma mientras recorre el árbol y busca la
suma.
Ahora, veremos una ejecución diferente, con la expresión proporcionada por sum1 :
C#
Resultados
Found Addition Expression
Found Constant: 1
Left is: 1
Found Constant: 2
Left is: 2
Found Constant: 3
Left is: 3
Found Constant: 4
Right is: 4
Computed sum: 7
Right is: 7
Computed sum: 9
Right is: 9
Computed sum: 10
10
Aunque la respuesta final es la misma, el recorrido del árbol es diferente. Los nodos se
recorren en un orden diferente, porque el árbol se construyó con diferentes operaciones
que se producen en primer lugar.
C#
return Visit(expression);
if (b.NodeType == ExpressionType.AndAlso)
return base.VisitBinary(b);
C#
Console.WriteLine(expr);
Console.WriteLine(modifiedExpr);
*/
El código crea una expresión que contiene una operación AND condicional. Luego crea
una instancia de la clase AndAlsoModifier y pasa la expresión al método Modify de esta
clase. Se generan los árboles de expresiones tanto originales como modificados para
mostrar el cambio. Compile y ejecute la aplicación.
Más información
En este ejemplo se muestra un pequeño subconjunto del código que se compilaría para
recorrer e interpretar los algoritmos representados por un árbol de expresión. Para
información sobre la compilación de una biblioteca de propósito general que traduce
árboles de expresión a otro lenguaje, lea esta serie de Matt Warren. Describe en detalle
cómo traducir cualquier código que es posible encontrar en un árbol de expresión.
Expression trees represent code in a tree-like data structure, where each node is an
expression, for example, a method call or a binary operation such as x < y .
If you have used LINQ, you have experience with a rich library where the Func types are
part of the API set. (If you aren't familiar with LINQ, you probably want to read the LINQ
tutorial and the article about lambda expressions before this one.) Expression Trees
provide richer interaction with the arguments that are functions.
You write function arguments, typically using Lambda Expressions, when you create
LINQ queries. In a typical LINQ query, those function arguments are transformed into a
delegate the compiler creates.
You've likely already written code that uses Expression trees. Entity Framework's LINQ
APIs accept Expression trees as the arguments for the LINQ Query Expression Pattern.
That enables Entity Framework to translate the query you wrote in C# into SQL that
executes in the database engine. Another example is Moq , which is a popular
mocking framework for .NET.
When you want to have a richer interaction, you need to use Expression Trees. Expression
Trees represent code as a structure that you examine, modify, or execute. These tools
give you the power to manipulate code during run time. You write code that examines
running algorithms, or injects new capabilities. In more advanced scenarios, you modify
running algorithms and even translate C# expressions into another form for execution in
another environment.
You compile and run code represented by expression trees. Building and running
expression trees enables dynamic modification of executable code, the execution of
LINQ queries in various databases, and the creation of dynamic queries. For more
information about expression trees in LINQ, see How to use expression trees to build
dynamic queries.
Expression trees are also used in the dynamic language runtime (DLR) to provide
interoperability between dynamic languages and .NET and to enable compiler writers to
emit expression trees instead of Microsoft intermediate language (MSIL). For more
information about the DLR, see Dynamic Language Runtime Overview.
You can have the C# or Visual Basic compiler create an expression tree for you based on
an anonymous lambda expression, or you can create expression trees manually by using
the System.Linq.Expressions namespace.
When a lambda expression is assigned to a variable of type Expression<TDelegate>, the
compiler emits code to build an expression tree that represents the lambda expression.
The C# compiler generates expression trees only from expression lambdas (or single-
line lambdas). It can't parse statement lambdas (or multi-line lambdas). For more
information about lambda expressions in C#, see Lambda Expressions.
The following code examples demonstrate how to have the C# compiler create an
expression tree that represents the lambda expression num => num < 5 .
C#
You create expression trees in your code. You build the tree by creating each node and
attaching the nodes into a tree structure. You learn how to create expressions in the
article on building expression trees.
Expression trees are immutable. If you want to modify an expression tree, you must
construct a new expression tree by copying the existing one and replacing nodes in it.
You use an expression tree visitor to traverse the existing expression tree. For more
information, see the article on translating expression trees.
Once you build an expression tree, you execute the code represented by the expression
tree.
Limitations
There are some newer C# language elements that don't translate well into expression
trees. Expression trees can't contain await expressions, or async lambda expressions.
Many of the features added in C# 6 and later don't appear exactly as written in
expression trees. Instead, newer features are exposed in expression trees in the
equivalent, earlier syntax, where possible. Other constructs aren't available. It means that
code that interprets expression trees works the same when new language features are
introduced. However, the expression trees Even with these limitations, expression trees
do enable you to create dynamic algorithms that rely on interpreting and modifying
code that is represented as a data structure. It enables rich libraries such as Entity
Framework to accomplish what they do.
Expression trees won't support new expression node types. It would be a breaking
change for all libraries interpreting expression trees to introduce new node types. The
following list includes most C# language elements that can't be used:
Conditional methods that have been removed
base access
Method group expressions, including address-of (&) a method group, and
anonymous method expressions
References to local functions
Statements, including assignment ( = ) and statement bodied expressions
Partial methods with only a defining declaration
Unsafe pointer operations
dynamic operations
Coalescing operators with null or default literal left side, null coalescing
assignment, and the null propagating operator (?.)
Multi-dimensional array initializers, indexed properties, and dictionary initializers
throw expressions
Accessing static virtual or abstract interface members
Lambda expressions that have attributes
Interpolated strings
UTF-8 string conversions or UTF-8 string literals
Method invocations using variable arguments, named arguments or optional
arguments
Expressions using System.Index or System.Range, index "from end" (^) operator or
range expressions (..)
async lambda expressions or await expressions, including await foreach and await
using
Tuple literals, tuple conversions, tuple == or !=, or with expressions
Discards (_), deconstructing assignment, pattern matching is operator or the
pattern matching switch expression
COM call with ref omitted on the arguments
ref, in or out parameters, ref return values, out arguments, or any values of ref
struct type
Interoperability Overview
Artículo • 25/02/2023 • Tiempo de lectura: 3 minutos
.NET enables interoperability with unmanaged code through platform invoke services,
the System.Runtime.InteropServices namespace, C++ interoperability, and COM
interoperability (COM interop).
Platform Invoke
Platform invoke is a service that enables managed code to call unmanaged functions
implemented in dynamic link libraries (DLLs), such as the Microsoft Windows API. It
locates and invokes an exported function and marshals its arguments (integers, strings,
arrays, structures, and so on) across the interoperation boundary as needed.
For more information, see Consuming Unmanaged DLL Functions and How to use
platform invoke to play a WAV file.
7 Nota
C++ Interop
You can use C++ interop, also known as It Just Works (IJW), to wrap a native C++ class.
C++ interop enables code authored in C# or another .NET language to access it. You
write C++ code to wrap a native DLL or COM component. Unlike other .NET languages,
Visual C++ has interoperability support that enables managed and unmanaged code in
the same application and even in the same file. You then build the C++ code by using
the /clr compiler switch to produce a managed assembly. Finally, you add a reference to
the assembly in your C# project and use the wrapped objects just as you would use
other managed classes.
1. Locate a COM component to use and register it. Use regsvr32.exe to register or
un–register a COM DLL.
2. Add to the project a reference to the COM component or type library.
When you
add the reference, Visual Studio uses the Tlbimp.exe (Type Library Importer), which
takes a type library as input, to output a .NET interop assembly. The assembly, also
named a runtime callable wrapper (RCW), contains managed classes and interfaces
that wrap the COM classes and interfaces that are in the type library. Visual Studio
adds to the project a reference to the generated assembly.
3. Create an instance of a class defined in the RCW. Creating an instance of that class
creates an instance of the COM object.
4. Use the object just as you use other managed objects. When the object is
reclaimed by garbage collection, the instance of the COM object is also released
from memory.
For more information, see Exposing COM Components to the .NET Framework.
Exposing C# to COM
COM clients can consume C# types that have been correctly exposed. The basic steps to
expose C# types are as follows:
For more information, see Exposing .NET Framework Components to COM and Example
COM Class.
See also
Improving Interop Performance
Introduction to Interoperability between COM and .NET
Introduction to COM Interop in Visual Basic
Marshaling between Managed and Unmanaged Code
Interoperating with Unmanaged Code
Control de versiones en C#
Artículo • 18/02/2023 • Tiempo de lectura: 6 minutos
Creación de bibliotecas
Como desarrollador que ha creado bibliotecas de .NET para uso público, probablemente
se ha encontrado en situaciones en las que tiene que implementar nuevas
actualizaciones. Cómo realizar este proceso es muy importante, ya que necesita
asegurarse de que existe una transición sin problemas del código existente a la versión
nueva de su biblioteca. Aquí se muestran algunos aspectos para tener en cuenta a la
hora de crear una versión nueva:
versiones anteriores
Métodos virtuales: cuando hace que un método virtual sea no virtual en la versión
nueva, significa que los proyectos que reemplacen ese método tendrán que
actualizarse. Esto es un cambio brusco enorme y se desaconseja totalmente.
Firmas de método: cuando actualizar un comportamiento del método requiere que
también se cambie su firma, en su lugar se debe crear una sobrecarga de manera
que el código que llama a ese método siga funcionando.
Siempre puede
manipular la firma del método anterior para llamar a la firma del método nuevo,
de manera que la implementación siga siendo coherente.
Atributo obsoleto: puede usar este atributo en el código para especificar clases o
miembros de clases que han quedado obsoletos y que probablemente se quiten
en versiones futuras. Esto garantiza que los desarrolladores que usen su biblioteca
estén mejor preparados para los cambios bruscos.
Argumentos de método opcionales: cuando hace que los argumentos de método
opcionales anteriores sean obligatorios o cambien su valor predeterminado, se
tendrá que actualizar todo el código que no proporcione esos argumentos.
7 Nota
Hacer que los argumentos obligatorios sean opcionales debe tener un efecto muy
pequeño, especialmente si no cambia el comportamiento del método.
Consumo de bibliotecas
Como desarrollador que consume bibliotecas .NET creadas por otros desarrolladores, es
probable que sea consciente de que una nueva versión de una biblioteca puede que no
sea completamente compatible con su proyecto y, a menudo, puede que tenga que
actualizar su código para trabajar con esos cambios.
XML
<dependentAssembly>
<assemblyIdentity name="ReferencedLibrary"
publicKeyToken="32ab4ba45e0a69a1" culture="en-us" />
</dependentAssembly>
7 Nota
C#
b.MyMethod();
d.MyMethod();
Salida
Consola
A base method
A derived method
override
El modificador override significa que una implementación derivada extiende la
implementación de un miembro de clase base en lugar de ocultarlo. El miembro de
clase base necesita que se le aplique el modificador virtual .
C#
Salida
Consola
Conceptos generales de C#
Hay varios trucos y sugerencias que son habituales entre los desarrolladores de C#:
Compare cadenas.
Modifique el contenido de una cadena.
Determine si una cadena representa un número.
Use String.Split para separar cadenas.
Combine varias cadenas en una.
Busque texto en una cadena.
Control de excepciones
Los programas de .NET informan de que un método no se ha ejecutado correctamente y
de que se han generado excepciones. En estos artículos aprenderá a trabajar con
excepciones.
Delegados y eventos
Los delegados y los eventos proporcionan capacidad para las estrategias que implican
bloques de código sin una conexión directa.
Prácticas de LINQ
LINQ permite escribir código para consultar cualquier origen de datos que admita su
patrón de expresión de consultas. Estos artículos le ayudarán a comprender el patrón y
a trabajar con orígenes de datos diferentes.
7 Nota
Este código divide una frase común en una matriz de cadenas para cada palabra.
C#
string phrase = "The quick brown fox jumps over the lazy dog.";
System.Console.WriteLine($"<{word}>");
Todas las instancias de un carácter separador generan un valor en la matriz devuelta. Los
caracteres separadores consecutivos generan la cadena vacía como un valor en la matriz
devuelta. Puede ver cómo se crea una cadena vacía en el ejemplo siguiente, en el que se
usa el carácter de espacio como separador.
C#
string phrase = "The quick brown fox jumps over the lazy dog.";
System.Console.WriteLine($"<{word}>");
Este comportamiento facilita formatos como los de los archivos de valores separados
por comas (CSV) que representan datos tabulares. Las comas consecutivas representan
una columna en blanco.
C#
System.Console.WriteLine($"<{word}>");
C#
System.Console.WriteLine($"<{word}>");
String.Split puede tomar una matriz de cadenas (secuencias de caracteres que actúan
como separadores para analizar la cadena de destino, en lugar de caracteres
individuales).
C#
System.Console.WriteLine(word);
Vea también
Extracción de elementos de una cadena
Guía de programación de C#
Cadenas
Expresiones regulares de .NET
Procedimiento para concatenar varias
cadenas (Guía de C#)
Artículo • 22/09/2022 • Tiempo de lectura: 4 minutos
Concatenación es el proceso de anexar una cadena al final de otra cadena. Las cadenas
se concatenan con el operador + . En el caso de los literales y las constantes de cadena,
la concatenación se produce en tiempo de compilación, y no en tiempo de ejecución. En
cambio, para las variables de cadena, la concatenación solo se produce en tiempo de
ejecución.
7 Nota
Literales de cadena
En el ejemplo siguiente se divide un literal de cadena larga en cadenas más pequeñas
para mejorar la legibilidad en el código fuente. El código concatena las cadenas más
pequeñas para crear el literal de cadena larga. Los elementos se concatenan en una sola
cadena en tiempo de compilación. No existe ningún costo de rendimiento en tiempo de
ejecución independientemente del número de cadenas implicadas.
C#
string text = "Historically, the world of data and the world of objects " +
"have not been well integrated. Programmers work in C# or Visual Basic " +
"and also in SQL or XQuery. On the one side are concepts such as classes, "
+
"objects, fields, inheritance, and .NET Framework APIs. On the other side "
+
"are tables, columns, rows, nodes, and separate languages for dealing with "
+
"them. Data types often require translation between the two worlds; there
are " +
"IntelliSense support in the IDE. Transferring data from SQL tables or XML
trees to " +
System.Console.WriteLine(text);
Operadores + y +=
Para concatenar variables de cadena, puede usar los operadores + o += , la
interpolación de cadena o los métodos String.Format, String.Concat, String.Join o
StringBuilder.Append. El operador + es sencillo de usar y genera un código intuitivo.
Aunque use varios operadores + en una instrucción, el contenido de la cadena se
copiará solo una vez. En el código siguiente se muestran ejemplos del uso de los
operadores + y += para concatenar cadenas:
C#
string str = "Hello " + userName + ". Today is " + dateString + ".";
System.Console.WriteLine(str);
System.Console.WriteLine(str);
Interpolación de cadenas
En algunas expresiones, es más fácil concatenar cadenas mediante la interpolación de
cadena, como se muestra en este código:
C#
System.Console.WriteLine(str);
System.Console.WriteLine(str);
7 Nota
String.Format
Otro método para concatenar cadenas es String.Format. Este método funciona bien
cuando se crea una cadena a partir de un número reducido de cadenas de componente.
StringBuilder
En otros casos, puede combinar cadenas en un bucle, donde no sabe cuántas cadenas
de origen se combinan, y el número real de cadenas de origen puede ser elevado. La
clase StringBuilder se diseñó para estos escenarios. El código siguiente usa el método
Append de la clase StringBuilder para concatenar cadenas.
C#
sb.AppendLine(i.ToString());
System.Console.WriteLine(sb.ToString());
Puede obtener más información sobre las razones para elegir la concatenación de
cadenas o sobre la clase StringBuilder.
String.Concat o String.Join
Otra opción para combinar cadenas a partir de una colección consiste en usar el
método String.Concat. Use el método String.Join si las cadenas de origen se deben
separar con un delimitador. El código siguiente combina una matriz de palabras usando
ambos métodos:
C#
System.Console.WriteLine(unreadablePhrase);
System.Console.WriteLine(readablePhrase);
LINQ y Enumerable.Aggregate
Por último, puede usar LINQ y el método Enumerable.Aggregate para combinar cadenas
a partir de una colección. Este método combina las cadenas de origen mediante una
expresión lambda. La expresión lambda realiza el trabajo de agregar cada cadena a la
acumulación existente. En el ejemplo siguiente se combina una matriz de palabras y se
agrega un espacio entre cada palabra de la matriz:
C#
System.Console.WriteLine(phrase);
Esta opción puede provocar más asignaciones que otros métodos para concatenar
colecciones, ya que crea una cadena intermedia para cada iteración. Si la optimización
del rendimiento es fundamental, considere la clase StringBuilder o los métodos
String.Concat o String.Join para concatenar una colección, en vez de
Enumerable.Aggregate .
Vea también
String
StringBuilder
Guía de programación de C#
Cadenas
Cómo buscar cadenas
Artículo • 15/11/2022 • Tiempo de lectura: 4 minutos
Puede usar dos estrategias principales para buscar texto en cadenas. Los métodos de la
clase String buscan un texto concreto. Las expresiones regulares buscan patrones en el
texto.
7 Nota
C#
Console.WriteLine($"\"{factMessage}\"");
// For user input and strings that will be displayed to the end user,
C#
Console.WriteLine($"\"{factMessage}\"");
// the first index is moved to the character just after the first string.
En el ejemplo de código siguiente, se busca la palabra "the" o "their" en una oración, sin
distinción entre mayúsculas y minúsculas. El método estático Regex.IsMatch realiza la
búsqueda. Se proporciona la cadena de búsqueda y un patrón de búsqueda. En este
caso, un tercer argumento especifica que la búsqueda no distingue mayúsculas de
minúsculas. Para obtener más información, vea
System.Text.RegularExpressions.RegexOptions.
Modelo Significado
C#
string[] sentences =
};
Console.Write($"{s,24}");
if (System.Text.RegularExpressions.Regex.IsMatch(s, sPattern,
System.Text.RegularExpressions.RegexOptions.IgnoreCase))
else
Console.WriteLine();
Sugerencia
Los métodos string suelen ser mejores opciones cuando se busca una cadena
exacta. Las expresiones regulares son más adecuadas cuando se busca algún
patrón en una cadena de origen.
Modelo Significado
C#
string[] numbers =
"123-555-0190",
"444-234-22450",
"690-555-0178",
"146-893-232",
"146-555-0122",
"4007-555-0111",
"407-555-0111",
"407-2-5555",
"407-555-8974",
"407-2ab-5555",
"690-555-8148",
"146-893-232-"
};
Console.Write($"{s,14}");
if (System.Text.RegularExpressions.Regex.IsMatch(s, sPattern))
{
Console.WriteLine(" - valid");
else
Console.WriteLine(" - invalid");
Este patrón de búsqueda sencillo coincide con muchas cadenas válidas. Las expresiones
regulares son mejores para buscar o validar con respecto a un patrón, en lugar de una
cadena de texto sencilla.
Vea también
Guía de programación de C#
Cadenas
LINQ y cadenas
System.Text.RegularExpressions.Regex
Expresiones regulares de .NET
Lenguaje de expresiones regulares: referencia rápida
Procedimientos recomendados para el uso de cadenas en .NET
Procedimiento para modificar el
contenido de cadenas en C#
Artículo • 29/11/2022 • Tiempo de lectura: 6 minutos
En este artículo se muestran varias técnicas para producir una string modificando una
string existente. Todas las técnicas mostradas devuelven el resultado de las
modificaciones como un objeto string nuevo. Para demostrar que las cadenas
originales y modificadas son instancias distintas, los ejemplos almacenan el resultado en
una variable nueva. Al ejecutar cada ejemplo, se puede examinar tanto el objeto string
original como el objeto string nuevo y modificado.
7 Nota
En este artículo se muestran varias técnicas. Puede reemplazar el texto existente. Puede
buscar patrones y reemplazar el texto coincidente por otro texto. Puede tratar una
cadena con una secuencia de caracteres. También puede usar métodos de conveniencia
para eliminar espacios en blanco. Elija la técnica con mayor coincidencia con el
escenario.
Reemplazo de texto
Con el código siguiente se crea una cadena mediante el reemplazo de texto con un
sustituto.
C#
C#
Console.WriteLine(source);
Console.WriteLine(replacement);
La cadena de origen se mantiene y se devuelve una cadena nueva con los reemplazos.
C#
Console.WriteLine($"<{source}>");
Console.WriteLine($"<{trimmedResult}>");
Console.WriteLine($"<{trimLeading}>");
Console.WriteLine($"<{trimTrailing}>");
Eliminación de texto
Puede quitar texto de una cadena con el método String.Remove. Este método quita un
número de caracteres que comienzan con un índice específico. En el siguiente ejemplo
se muestra cómo usar String.IndexOf seguido por Remove para quitar texto de una
cadena:
C#
int i = source.IndexOf(toRemove);
if (i >= 0)
Console.WriteLine(source);
Console.WriteLine(result);
Las expresiones regulares son más útiles al buscar y reemplazar texto que sigue un
patrón, en vez de texto que ya conoce. Para obtener más información, vea
Procedimiento para buscar cadenas. Con el patrón de búsqueda "the\s" se busca la
palabra "the" seguida de un carácter de espacio en blanco. Con esa parte del patrón se
asegura de que no se busca "there" en la cadena de origen. Para obtener más
información sobre los elementos de lenguaje de expresiones regulares, vea Lenguaje de
expresiones regulares - Referencia rápida.
C#
string source = "The mountains are still there behind the clouds today.";
// using System.Text.RegularExpressions
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
Console.WriteLine(source);
string LocalReplaceMatchCase(System.Text.RegularExpressions.Match
matchExpression)
if (Char.IsUpper(matchExpression.Value[0]))
replacementBuilder[0] = Char.ToUpper(replacementBuilder[0]);
return replacementBuilder.ToString();
else
return replaceWith;
C#
string phrase = "The quick brown fox jumps over the fence";
Console.WriteLine(phrase);
if (animalIndex != -1)
phraseAsChars[animalIndex++] = 'c';
phraseAsChars[animalIndex++] = 'a';
phraseAsChars[animalIndex] = 't';
Console.WriteLine(updatedPhrase);
C#
strContent[0] = '0';
strContent[1] = '1';
strContent[i + 2] = charArray[i];
});
Console.WriteLine(result);
Puede modificar una cadena en un bloque fijo con código no seguro, pero es
totalmente desaconsejable modificar el contenido de la cadena una vez que se ha
creado. Si lo hace, puede haber problemas imprevisibles. Por ejemplo, si alguien se
conecta a una cadena que tiene el mismo contenido que la suya, esa persona obtendrá
la copia de usted y no esperará que usted modifique la cadena.
Vea también
Expresiones regulares de .NET
Lenguaje de expresiones regulares: referencia rápida
Cómo comparar cadenas en C#
Artículo • 22/09/2022 • Tiempo de lectura: 11 minutos
Las cadenas se comparan para responder a una de estas dos preguntas: "¿Son estas dos
cadenas iguales?" o "¿En qué orden deben colocarse estas cadenas al ordenarlas?"
Esas dos preguntas se complican por factores que influyen en las comparaciones de
cadenas:
7 Nota
String.Equals
String.Equality y String.Inequality; es decir, los operadores de igualdad== y !=,
respectivamente,
realizan una comparación de ordinales que distingue mayúsculas de minúsculas. En el
caso de String.Equals, se puede proporcionar un argumento StringComparison para
alterar sus reglas de ordenación. En el siguiente ejemplo se muestra que:
C#
C#
if (comparison < 0)
else
Comparaciones lingüísticas
También se pueden ordenar cadenas mediante reglas lingüísticas para la referencia
cultural actual.
A veces, esto se denomina “criterio de ordenación de palabras”. Cuando
se realiza una comparación lingüística, algunos caracteres Unicode no alfanuméricos
pueden tener asignados pesos especiales. Por ejemplo, el guion ("-") podría tener
asignado un peso pequeño, por lo que las cadenas "coop" y "co-op" aparecerían una
junto a la otra en una ordenación. Además, algunos caracteres Unicode pueden ser
equivalentes a una secuencia de instancias de Char. En el ejemplo siguiente se usa la
frase "Ellos bailan en la calle" en alemán. Usa "ss" (U+0073 U+0073) en una cadena y "ß"
(U+00DF) en otra. Lingüísticamente (en Windows), "ss" es igual que el carácter "ß" en
alemán en las referencias culturales "en-US" y "de-DE".
C#
showComparison(first, second);
showComparison(word, words);
showComparison(word, other);
showComparison(words, other);
if (compareLinguistic < 0)
else
if (compareOrdinal < 0)
else
C#
if (compareLinguistic < 0)
else
if (compareOrdinal < 0)
else
En este ejemplo se muestra cómo ordenar una matriz de cadenas con la referencia
cultural actual:
C#
@"c:\public\textfile.txt",
@"c:\public\textFile.TXT",
@"c:\public\Text.txt",
@"c:\public\testfile2.txt"
};
Console.WriteLine("Non-sorted order:");
Console.WriteLine($" {s}");
Console.WriteLine("\n\rSorted order:");
Array.Sort(lines, StringComparer.CurrentCulture);
Console.WriteLine($" {s}");
Una vez que se ordena la matriz, puede buscar entradas mediante una búsqueda
binaria. Una búsqueda binaria empieza en medio de la colección para determinar qué
mitad de la colección debe contener la cadena buscada. Cada comparación posterior
divide la parte restante de la colección por la mitad. La matriz se ordena con el
elemento StringComparer.CurrentCulture. La función local ShowWhere muestra
información sobre dónde se encuentra la cadena. Si no se encuentra la cadena, el valor
devuelto indica dónde estaría si se encontrara.
C#
@"c:\public\textfile.txt",
@"c:\public\textFile.TXT",
@"c:\public\Text.txt",
@"c:\public\testfile2.txt"
};
Array.Sort(lines, StringComparer.CurrentCulture);
ShowWhere<string>(lines, result);
if (index < 0)
index = ~index;
if (index == 0)
Console.Write("beginning of sequence and ");
else
if (index == array.Length)
Console.WriteLine("end of sequence.");
else
Console.WriteLine($"{array[index]}.");
else
C#
@"c:\public\textfile.txt",
@"c:\public\textFile.TXT",
@"c:\public\Text.txt",
@"c:\public\testfile2.txt"
};
Console.WriteLine("Non-sorted order:");
Console.WriteLine($" {s}");
Console.WriteLine("\n\rSorted order:");
Console.WriteLine($" {s}");
C#
@"c:\public\textfile.txt",
@"c:\public\textFile.TXT",
@"c:\public\Text.txt",
@"c:\public\testfile2.txt"
};
ShowWhere<string>(lines, result);
if (index < 0)
index = ~index;
if (index == 0)
Console.Write("beginning of sequence and ");
else
if (index == collection.Count)
Console.WriteLine("end of sequence.");
else
Console.WriteLine($"{collection[index]}.");
else
C#
if (String.ReferenceEquals(a, b))
Console.WriteLine("a and b are interned.");
else
string c = String.Copy(a);
if (String.ReferenceEquals(a, c))
Console.WriteLine("a and c are interned.");
else
7 Nota
Cuando se prueba la igualdad de cadenas, debe usar los métodos que especifican
explícitamente el tipo de comparación que va a realizar. El código se vuelve mucho
más legible y fácil de mantener. Use sobrecargas de los métodos de las clases
System.String y System.Array que toman un parámetro de enumeración
StringComparison. Especifique qué tipo de comparación se va a realizar. Evite usar
los operadores == y != cuando pruebe la igualdad. Los métodos de instancia
String.CompareTo siempre realizan una comparación ordinal con distinción entre
mayúsculas y minúsculas. Son adecuados principalmente para ordenar
alfabéticamente las cadenas.
Puede internalizar una cadena o recuperar una referencia a una cadena internalizada
existente llamando al método String.Intern. Para determinar si una cadena se aplica el
método Intern, llame al método String.IsInterned.
Vea también
System.Globalization.CultureInfo
System.StringComparer
Cadenas
Comparación de cadenas
Globalizar y localizar aplicaciones
Procedimiento para detectar
excepciones no compatibles con CLS
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Algunos lenguajes. NET, incluido C++/CLI, permiten que los objetos inicien excepciones
que no se derivan de Exception. Dichas excepciones se denominan excepciones de no
compatibilidad con CLS o no excepciones. En C# no se pueden producir excepciones de
no compatibilidad con CLS, pero se pueden detectar de dos formas:
Dentro de un bloque catch general (un bloque catch sin un tipo de excepción
especificado) que se coloca detrás de todos los demás bloques catch .
Use este método cuando quiera realizar alguna acción (como escribir en un
archivo de registro) en respuesta a las excepciones de no compatibilidad con CLS y
no necesita tener acceso a la información de excepción. De forma predeterminada,
el Common Language Runtime ajusta todas las excepciones. Para deshabilitar este
comportamiento, agregue este atributo de nivel de ensamblado en el código,
normalmente en el archivo AssemblyInfo.cs: [assembly:
RuntimeCompatibilityAttribute(WrapNonExceptionThrows = false)] .
Ejemplo
En el ejemplo siguiente se muestra cómo detectar una excepción de no compatibilidad
con CLS iniciada desde una biblioteca de clases escrita en C++/CLI. Tenga en cuenta que
en este ejemplo, el código de cliente de C# conoce por adelantado que el tipo de
excepción que se inicia es System.String. Puede convertir la propiedad
RuntimeWrappedException.WrappedException de vuelta a su tipo original siempre que
el tipo sea accesible desde su código.
C#
try
myClass.TestThrow();
catch (RuntimeWrappedException e)
{
if (s != null)
Console.WriteLine(s);
Consulte también
RuntimeWrappedException
Excepciones y control de excepciones
SDK de .NET Compiler Platform
Artículo • 15/02/2023 • Tiempo de lectura: 7 minutos
Los analizadores y las correcciones de código usan el análisis estático para comprender
el código. No ejecutan el código o proporcionan otras ventajas de pruebas. Sin
embargo, pueden señalar prácticas que suelen dar lugar a errores, código que no se
puede mantener o validación de guías estándar.
Además de los analizadores y las correcciones de código, el SDK de
.NET Compiler Platform también le permite compilar refactorizaciones de código.
También proporciona un único conjunto de API que le permite examinar y entender un
código base de C# o Visual Basic. Dado que puede usar este código base único, puede
escribir analizadores y correcciones de código más fácilmente aprovechando las API de
análisis sintáctico y semántico que proporciona el SDK de .NET Compiler Platform. Una
vez liberado de la ardua tarea de replicar el análisis realizado por el compilador, puede
concentrarse en la tarea más específica de encontrar y corregir errores de codificación
comunes para el proyecto o la biblioteca.
Una ventaja menor es que los analizadores y las correcciones de código son más
pequeños y usan muchos menos memoria cuando se cargan en Visual Studio que si
escribiera su propio código base para entender el código de un proyecto.
Aprovechando las mismas clases que usa el compilador y Visual Studio, puede crear sus
propias herramientas de análisis estático. Esto significa que el equipo puede usar los
analizadores y las correcciones de código sin un impacto perceptible en el rendimiento
del IDE.
Sugerencia
Antes de compilar su propio analizador, consulte los integrados. Para obtener más
información, vea Reglas de estilo del código.
Pasos siguientes
El SDK de .NET Compiler Platform incluye los modelos de objetos de idioma más
recientes para generación de código, análisis y refactorización. Esta sección proporciona
información general conceptual del SDK de .NET Compiler Platform. Encontrará más
detalles en las secciones de guías de inicio rápido, ejemplos y tutoriales.
Puede obtener más información sobre los conceptos del SDK de .NET Compiler Platform
en estos cinco temas:
Estas API son las mismas que usa Visual Studio. Por ejemplo, las características de
formato y esquema del código usan los árboles de sintaxis, las características de
navegación y el Examinador de objetos usan la tabla de símbolos, las refactorizaciones
e Ir a definición usan el modelo semántico, y Editar y continuar usa todos ellos, incluida
la API de emisión.
Capas de API
El SDK del compilador de .NET consta de varias capas de API: de compilador, de
diagnóstico, de scripting y de área de trabajo.
API de compilador
La capa de compilador contiene los modelos de objetos que corresponden con la
información expuesta en cada fase de la canalización de compilador, sintáctica y
semántica. La capa de compilador también contiene una instantánea inmutable de una
sola invocación de un compilador, incluidas las referencias de ensamblado, las opciones
del compilador y los archivos de código fuente. Hay dos API distintas que representan el
lenguaje C# y el lenguaje Visual Basic. Las dos API son similares en forma, aunque están
adaptadas para lograr una alta fidelidad con cada lenguaje. Esta capa no tiene
dependencias en componentes de Visual Studio.
API de diagnóstico
Como parte de su análisis, el compilador puede generar un conjunto de diagnósticos
que abarcan desde errores de sintaxis, semántica y asignación definitiva hasta distintas
advertencias y diagnósticos informativos. La capa de la API de compilador expone
diagnósticos a través de una API extensible que permite incluir analizadores definidos
por el usuario en el proceso de compilación. Permite generar diagnósticos definidos por
el usuario, como los de herramientas como StyleCop, junto con diagnósticos definidos
por el compilador. La generación de diagnósticos de este modo tiene la ventaja de
integrarse de forma natural con herramientas como MSBuild y Visual Studio, que
dependen de diagnósticos de experiencias como detener una compilación según una
directiva y mostrar subrayados ondulados activos en el editor y sugerir correcciones de
código.
API de scripting
Las API de hospedaje y scripting forman parte de la capa de compilador. Puede usarlas
para ejecutar fragmentos de código y acumular un contexto de ejecución en tiempo de
ejecución.
El REPL (bucle de lectura, evaluación e impresión) interactivo de C# usa estas
API. El REPL permite usar C# como lenguaje de scripting, al ejecutar el código de forma
interactiva mientras se escribe.
El árbol de sintaxis es una estructura de datos fundamental e inmutable expuesta por las
API del compilador. Estos árboles representan la estructura léxica y sintáctica del código
fuente. Tienen dos importantes finalidades:
Árboles de sintaxis
Los árboles de sintaxis son la estructura principal usada para la compilación, el análisis
de código, los enlaces, la refactorización, las características de IDE y la generación de
código. Ninguna parte del código fuente se entiende sin que primero se haya
identificado y clasificado en alguno de los elementos de lenguaje estructural conocidos.
Contienen toda la información de origen con fidelidad completa. Esto significa que
el árbol de sintaxis contiene cada fragmento de información del texto de origen,
cada construcción gramatical, cada token léxico y todo lo demás, incluidos los
espacios en blanco, los comentarios y las directivas de preprocesador. Por ejemplo,
cada literal mencionado en el origen se representa exactamente como se ha
escrito. Los árboles de sintaxis también capturan los errores del código fuente
cuando el programa está incompleto o tiene un formato incorrecto mediante la
representación de los tokens omitidos o que faltan.
Pueden generar el texto exacto mediante el que se analizaron. Es posible obtener
la representación de texto del subárbol cuya raíz está en ese nodo desde cualquier
nodo de sintaxis. Esta posibilidad significa que los árboles de sintaxis se pueden
usar como una manera de crear y editar texto de origen. Al crear un árbol, de
manera implícita, ha creado el texto equivalente, mientras que, al crear uno a partir
de los cambios en uno existente, ha editado realmente el texto.
Son inmutables y seguros para subprocesos. Una vez obtenido un árbol, es una
instantánea del estado actual del código y nunca cambia. Esto permite que varios
usuarios interactúen con el mismo árbol de sintaxis a la vez en distintos
subprocesos sin que se produzca ningún bloqueo ni duplicación. Dado que los
árboles son inmutables y no permiten ninguna modificación directa, los métodos
de fábrica ayudan a crear y modificar los árboles de sintaxis mediante la creación
de instantáneas adicionales del árbol. Los árboles son eficaces en su forma de
volver a usar nodos subyacentes, así que es posible volver a crear una nueva
versión rápidamente y con poca memoria adicional.
Nodos de sintaxis
Los nodos de sintaxis son uno de los elementos principales de los árboles de sintaxis.
Estos nodos representan construcciones sintácticas como declaraciones, instrucciones,
cláusulas y expresiones. Cada categoría de nodos de sintaxis se representa mediante
una clase independiente derivada de Microsoft.CodeAnalysis.SyntaxNode. El conjunto
de clases de nodos no es extensible.
Todos los nodos de sintaxis son nodos no terminales del árbol de sintaxis, lo que
significa que siempre tienen otros nodos y tokens como elementos secundarios. Como
elemento secundario de otro nodo, cada nodo tiene un nodo principal al que se puede
acceder mediante la propiedad SyntaxNode.Parent. Dado que los nodos y los árboles
son inmutables, el elemento principal de un nodo nunca cambia. La raíz del árbol tiene
un elemento principal nulo.
Cada nodo tiene un método SyntaxNode.ChildNodes() que devuelve una lista de nodos
secundarios en orden secuencial según su posición en el texto de origen. Esta lista no
contiene tokens. Cada nodo también tiene métodos para examinar descendientes, como
DescendantNodes, DescendantTokens o DescendantTrivia, que representan una lista de
todos los nodos, tokens o curiosidades que existen en el subárbol cuya raíz está en ese
nodo.
Además, cada subclase de nodos de sintaxis expone los mismos elementos secundarios
mediante propiedades fuertemente tipadas. Por ejemplo, una clase de nodos
BinaryExpressionSyntax tiene tres propiedades adicionales específicas de los operadores
binarios: Left, OperatorToken y Right. El tipo de Left y Right es ExpressionSyntax, y el
tipo de OperatorToken es SyntaxToken.
Tokens de sintaxis
Los tokens de sintaxis son los terminales de la gramática del lenguaje y representan los
fragmentos sintácticos más pequeños del código. Nunca son elementos principales de
otros nodos o tokens. Los tokens de sintaxis constan de palabras clave, identificadores,
literales y signos de puntuación.
Por eficacia, el tipo SyntaxToken es un tipo de valor CLR. Por tanto, a diferencia de los
nodos de sintaxis, solo hay una estructura para todos los tipos de tokens con una
mezcla de propiedades que tienen significado según el tipo de token que se va a
representar.
Por ejemplo, un token de literal entero representa un valor numérico. Además del texto
de origen sin formato que abarca el token, el token de literal tiene una propiedad Value
que indica el valor entero descodificado exacto. El tipo de esta propiedad se considera
Object, ya que puede ser alguno de los tipos primitivos.
Curiosidades de sintaxis
Las curiosidades de sintaxis representan las partes del texto de origen que no son
realmente significativas para la correcta comprensión del código, como los espacios en
blanco, los comentarios y las directivas de preprocesador. Al igual que los tokens de
sintaxis, las curiosidades son tipos de valor. Se usa el tipo único
Microsoft.CodeAnalysis.SyntaxTrivia para describir todos los tipos de curiosidades.
Dado que las curiosidades no forman parte de la sintaxis normal del lenguaje y pueden
aparecer en cualquier lugar entre dos tokens, no se incluyen en el árbol de sintaxis
como elemento secundario de un nodo. Pero dado que son importantes a la hora de
implementar una característica como la refactorización y de mantener la plena fidelidad
con el texto de origen, existen como parte del árbol de sintaxis.
Puede acceder a las curiosidades si inspecciona las colecciones
SyntaxToken.LeadingTrivia o SyntaxToken.TrailingTrivia de un token. Cuando se analiza el
texto de origen, las secuencias de curiosidades se asocian a los tokens. En general, un
token es propietario de cualquier curiosidad que le preceda en la misma línea hasta el
siguiente token. Cualquier curiosidad situada después de esa línea se asocia al token
siguiente. El primer token del archivo de origen obtiene todas las curiosidades iniciales,
mientras que la última secuencia de curiosidades del archivo se agrega al último token
del archivo, que, de lo contrario, tiene ancho de cero.
A diferencia de los nodos y los tokens de sintaxis, las curiosidades de sintaxis no tienen
elementos principales. Pero, dado que forman parte del árbol y cada una está asociada a
un token único, se puede acceder al token con el que está asociada mediante la
propiedad SyntaxTrivia.Token.
Intervalos
Cada nodo, token o curiosidad conoce su posición dentro del texto de origen y el
número de caracteres del que se compone. Una posición de texto se representa como
un entero de 32 bits, que es un índice char de base cero. Un objeto TextSpan es la
posición inicial y un recuento de caracteres, ambos representados como enteros. Si
TextSpan tiene una longitud cero, hace referencia a una ubicación entre dos caracteres.
La propiedad Span es el intervalo de texto desde el principio del primer token del
subárbol del nodo al final del último token. Este intervalo no incluye ninguna curiosidad
inicial ni final.
La propiedad FullSpan es el intervalo de texto que incluye el intervalo normal del nodo,
así como el intervalo de cualquier curiosidad inicial o final.
Por ejemplo:
C#
if (x > 3)
|| // this is bad
El nodo de la instrucción dentro del bloque tiene un intervalo indicado por las plecas (|).
Incluye los caracteres throw new Exception("Not right."); . Las plecas dobles (||) indican
el intervalo completo. Incluye los mismos caracteres que el intervalo y los caracteres
asociados a las curiosidades inicial y final.
Tipos
Cada nodo, token o curiosidad tiene una propiedad SyntaxNode.RawKind, de tipo
System.Int32, que identifica el elemento de sintaxis exacto representado. Este valor se
puede convertir en una enumeración específica del lenguaje. Cada lenguaje, C# o Visual
Basic, tiene una sola enumeración SyntaxKind
(Microsoft.CodeAnalysis.CSharp.SyntaxKind y
Microsoft.CodeAnalysis.VisualBasic.SyntaxKind, respectivamente) que enumera todos los
posibles nodos, tokens y curiosidades de la gramática. Dicha conversión se puede
realizar automáticamente. Para ello, es necesario acceder a los métodos de extensión
CSharpExtensions.Kind o VisualBasicExtensions.Kind.
Por ejemplo, una sola clase BinaryExpressionSyntax tiene Left, OperatorToken y Right
como elementos secundarios. La propiedad Kind distingue si es un tipo AddExpression,
SubtractExpression o MultiplyExpression de nodo de sintaxis.
Sugerencia
Se recomienda comprobar los tipos con los métodos de extensión IsKind (para C#)
o IsKind (para VB).
Errores
Aunque el texto de origen contenga errores de sintaxis, se expone un árbol de sintaxis
completo con recorrido de ida y vuelta al origen. Si el analizador detecta código que no
se ajusta a la sintaxis definida del lenguaje, usa una de estas dos técnicas para crear un
árbol de sintaxis.
Los árboles de sintaxis representan la estructura léxica y sintáctica del código fuente.
Aunque esta información por sí misma basta para describir todas las declaraciones y la
lógica del origen, no es suficiente para identificar aquello a lo que se hace referencia. Un
nombre puede representar:
un tipo
un campo
un método
una variable local
Además de un modelo sintáctico del código fuente, un modelo semántico encapsula las
reglas del lenguaje, lo que le ofrece una manera sencilla de combinar correctamente los
identificadores con el elemento de programa correcto al que se hace referencia.
Compilación
Una compilación es una representación de todo lo necesario para compilar un
programa de C# o Visual Basic, lo que incluye todas las referencias de ensamblado, las
opciones de compilador y los archivos de origen.
Dado que toda esta información está en un solo lugar, los elementos incluidos en el
código fuente pueden describirse con más detalle. La compilación representa cada tipo
declarado, miembro o variable como un símbolo. La compilación contiene una serie de
métodos que ayudan a encontrar y relacionar los símbolos que se han declarado en el
código fuente o importado como metadatos desde un ensamblado.
Al igual que los árboles de sintaxis, las compilaciones son inmutables. Después de crear
una compilación, ni el usuario ni nadie con quien la comparta puede modificarla. Pero
puede crear una nueva compilación a partir de una existente, al especificar un cambio a
medida que lo realiza. Por ejemplo, podría crear una compilación igual en todos los
sentidos a una compilación existente, salvo que podría incluir un archivo de origen
adicional o una referencia de ensamblado.
Símbolos
Un símbolo representa un elemento diferenciado declarado por el código fuente o
importado desde un ensamblado como metadatos. Cada espacio de nombres, tipo,
método, propiedad, campo, evento, parámetro o variable local se representa mediante
un símbolo.
Una serie de métodos y propiedades del tipo Compilation ayudan a encontrar símbolos.
Por ejemplo, puede buscar el símbolo de un tipo declarado por su nombre de
metadatos común. También puede acceder a la tabla de símbolos completa como un
árbol de símbolos enraizado por el espacio de nombres global.
Los símbolos son similares en concepto al sistema de tipos de CLR representado por la
API System.Reflection, aunque son mejores en el aspecto de que modelan algo más que
tipos. Los espacios de nombres, las variables locales y las etiquetas son todos símbolos.
Además, los símbolos son una representación de conceptos del lenguaje, no de
conceptos de CLR. Hay mucha superposición, pero también muchas distinciones
significativas. Por ejemplo, un método Iterator de C# o Visual Basic es un único símbolo.
Pero si el método Iterator se traduce a metadatos de CLR, es un tipo y varios métodos.
Modelo semántico
Un modelo semántico representa toda la información semántica de un solo archivo de
origen. Puede usarlo para descubrir lo siguiente:
Los símbolos a los que se hace referencia en una ubicación concreta del origen.
El tipo resultante de cualquier expresión.
Todos los diagnósticos, que son errores y advertencias.
Cómo fluyen las variables hacia y desde las regiones del origen.
Las respuestas a preguntas más especulativas.
Trabajar con un área de trabajo
Artículo • 10/02/2023 • Tiempo de lectura: 3 minutos
Los entornos de host, como un IDE, proporcionan un área de trabajo que corresponde a
la solución abierta. También es posible usar este modelo fuera de un IDE con solo cargar
un archivo de solución.
Área de trabajo
Un área de trabajo es una representación activa de la solución como una colección de
proyectos, cada uno con una colección de documentos. Normalmente, un área de
trabajo está asociada a un entorno de host en continuo cambio a medida que el usuario
escribe o manipula las propiedades.
Un proyecto es una parte del modelo de solución general inmutable. Representa todos
los documentos de código de origen, las opciones de análisis y compilación, y las
referencias de ensamblado y de proyecto a proyecto. Desde un proyecto puede acceder
a la compilación correspondiente sin necesidad de determinar las dependencias del
proyecto ni de analizar los archivos de origen.
Resumen
Roslyn expone un conjunto de API de compilador y API de áreas de trabajo que
proporciona información detallada sobre el código fuente y que tiene plena fidelidad
con los lenguajes C# y Visual Basic. El SDK de .NET Compiler Platform reduce
notablemente la barrera para crear herramientas y aplicaciones centradas en el código.
Crea numerosas oportunidades para la innovación en áreas como la metaprogramación,
la generación y la transformación de código, el uso interactivo de los lenguajes C# y
Visual Basic, y la inserción de C# y Visual Basic en lenguajes específicos del dominio.
Explorar código con el Visualizador de
sintaxis Roslyn en Visual Studio
Artículo • 11/10/2022 • Tiempo de lectura: 10 minutos
Para familiarizarse con los conceptos usados en el SDK de .NET Compiler Platform, lea el
artículo introductorio. Proporciona una introducción a los árboles de sintaxis, los nodos,
los tokens y algunas curiosidades.
Visualizador de sintaxis
Syntax Visualizer permite inspeccionar el árbol de sintaxis del archivo de código de C#
o Visual Basic en la ventana del editor activo actual en el IDE de Visual Studio. El
visualizador se puede iniciar haciendo clic en Vista>Other Windows (Otras
ventanas)>Syntax Visualizer (Visualizador de sintaxis) . También puede usar la barra de
herramientas Inicio rápido en la esquina superior derecha. Escriba "syntax" y se
mostrará el comando para abrir el Visualizador de sintaxis.
Cree un nuevo proyecto con los comandos Archivo>Nuevo proyecto. Puede crear un
proyecto de Visual Basic o C#. Cuando Visual Studio abre el principal archivo de código
para este proyecto, el visualizador muestra el árbol de sintaxis correspondiente. Puede
abrir cualquier archivo de C# o Visual Basic existente en esta instancia de Visual Studio y
el visualizador mostrará el árbol de sintaxis de ese archivo. Si tiene varios archivos de
código abiertos dentro de Visual Studio, el visualizador muestra el árbol de sintaxis para
el archivo de código activo (el archivo de código que tiene el foco de teclado).
C#
Como se muestra en las imágenes anteriores, la ventana de herramientas del
visualizador muestra el árbol de sintaxis en la parte superior y una cuadrícula de
propiedades en la parte inferior. La cuadrícula de propiedades muestra las propiedades
del elemento que está seleccionado actualmente en el árbol, incluido el Tipo de .NET y
la Variante (SyntaxKind) del elemento.
Los árboles de sintaxis incluyen tres tipos de elementos: nodos, tokens y curiosidades.
Encontrará más información sobre estos tipos en el artículo Trabajar con sintaxis. Los
elementos de cada tipo se representan mediante un color diferente. Haga clic en el
botón “Leyenda” para saber más sobre los colores usados.
Cada elemento del árbol también muestra su propio intervalo. El intervalo está
comprendido por los índices (la posición inicial y la final) de ese nodo en el archivo de
texto. En el anterior ejemplo de C#, el token “UsingKeyword [0..5)” seleccionado tiene un
intervalo de cinco caracteres de ancho [0..5). La notación “[.)” significa que el índice
inicial forma parte del intervalo, pero el índice final no.
Pare de escribir en cuanto escriba Console. . Verá que el árbol ha marcado en rosa
algunos elementos. En este momento, hay errores (también denominados
“diagnósticos”) en el código escrito. Estos errores se adjuntan a los nodos, los tokens y
las curiosidades en el árbol de sintaxis. El visualizador muestra qué elementos tienen
errores adjuntados a ellos resaltando el fondo en color rosa. Puede inspeccionar los
errores en cualquier elemento marcado en rosa si desplaza el puntero sobre el
elemento. El visualizador muestra solo los errores sintácticos (los errores relacionados
con la sintaxis del código escrito) y no muestra los errores semánticos.
Gráficos de sintaxis
Haga clic con el botón derecho en cualquier elemento del árbol y haga clic en View
Directed Syntax Graph (Ver gráfico de sintaxis dirigido).
C#
Semántica de inspección
El Visualizador de sintaxis permite realizar una inspección rudimentaria de símbolos e
información semántica. Escriba double x = 1 + 1; dentro de Main() en el ejemplo de
C#. Después, seleccione la expresión 1 + 1 en la ventana del editor de código. El
visualizador resalta el nodo AddExpression en el visualizador. Haga clic con el botón
derecho en AddExpression y elija View Symbol (if any) [Ver símbolo (si existe)]. Tenga
en cuenta que la mayoría de los elementos de menú tienen el calificador "si existe". El
Visualizador de sintaxis inspecciona las propiedades de un nodo, incluidas las
propiedades que es posible que no estén presentes para todos los nodos.
Intente View Converted TypeSymbol (if any) [Ver TypeSymbol convertido (si existe)]
para el mismo nodo AddExpression. La cuadrícula de propiedades se actualiza para
indicar que, aunque el tipo de la expresión es Int32 , el tipo convertido de la expresión
es Double , como se muestra en esta imagen. Este nodo incluye información de símbolo
de tipo convertido porque la expresión Int32 se produce en un contexto donde se
debe convertir a Double . Esta conversión satisface el tipo Double especificado para la
variable x en el lado izquierdo del operador de asignación.
Por último, intente View Constant Value (if any) [Ver valor de constante (si existe)] para
el mismo nodo AddExpression. La cuadrícula de propiedades muestra que el valor de la
expresión es una constante en tiempo de compilación con el valor 2 .
El ejemplo anterior también se puede replicar en Visual Basic. Escriba Dim x As Double =
1 + 1 en un archivo de Visual Basic. Seleccione la expresión 1 + 1 en la ventana del
editor de código. El visualizador resalta el nodo AddExpression correspondiente en el
visualizador. Repita los pasos anteriores para AddExpression y deberían mostrarse
resultados idénticos.
Examine más código en Visual Basic. Actualice el archivo principal de Visual Basic con
este código:
VB
Imports C = System.Console
Module Program
C.WriteLine()
End Sub
End Module
Este código incluye un alias llamado C que se asigna al tipo System.Console en la parte
superior del archivo y usa este alias en Main() . Seleccione el uso de este alias, C en
C.WriteLine() , dentro del método Main() . El visualizador selecciona el nodo
Intente View AliasSymbol (if any) [Ver AliasSymbol (si existe)] para el mismo nodo
IdentifierName. La cuadrícula de propiedades indica que el identificador es un alias con
el nombre C que está enlazado al destino System.Console . En otras palabras, la
cuadrícula de propiedades proporciona información sobre el AliasSymbol
correspondiente al identificador C .
Inspeccione el símbolo correspondiente a cualquier tipo, método o propiedad
declarados. Seleccione el nodo correspondiente en el visualizador y haga clic en View
Symbol (if any) [Ver símbolo (si existe)]. Seleccione el método Sub Main() , incluido el
cuerpo del método. Haga clic en View Symbol (if any) [Ver símbolo (si existe)] para el
nodo SubBlock correspondiente en el visualizador. La cuadrícula de propiedades
muestra que MethodSymbol para este nodo SubBlock tiene el nombre Main con el tipo
de valor devuelto Void .
Los ejemplos de Visual Basic anteriores se pueden replicar fácilmente en C#. Escriba
using C = System.Console; en lugar de Imports C = System.Console para el alias. Los
pasos anteriores en C# producen resultados idénticos en la ventana del visualizador.
Las operaciones de inspección semántica solo están disponibles en los nodos. No están
disponibles en tokens o curiosidades. No todos los nodos tienen información semántica
interesante que inspeccionar. Cuando un nodo no tiene información semántica
interesante, al hacer clic en View * Symbol (if any) [Ver símbolo * (si existe)] se muestra
una cuadrícula de propiedades en blanco.
Puede leer más sobre las API para realizar análisis semánticos en el documento
introductorio Trabajar con semántica.
En este artículo se ofrece información general sobre los generadores de código fuente
que se incluye como parte del SDK de .NET Compiler Platform ("Roslyn"). Los
generadores de código fuente permiten a los desarrolladores de C# inspeccionar el
código de usuario a medida que se compila. El generador puede crear nuevos archivos
de código fuente de C# sobre la marcha que se agregan a la compilación del usuario.
De este modo, tiene código que se ejecuta durante la compilación. Además, inspecciona
el programa para generar archivos de código fuente adicionales que se compilan junto
con el resto del código.
Cuando se combinan, estas dos cosas son las que hacen que los generadores de código
fuente sean tan útiles. Puede inspeccionar el código de usuario con todos los metadatos
enriquecidos que el compilador crea durante la compilación. Después, el generador
vuelve a emitir código de C# en la misma compilación que se basa en los datos que ha
analizado. Si está familiarizado con los analizadores de Roslyn, puede pensar en los
generadores de código fuente como analizadores que pueden emitir código fuente de
C#.
Los generadores de código fuente se ejecutan como una fase de compilación que se
visualiza a continuación:
) Importante
Escenarios frecuentes
Existen tres enfoques generales para inspeccionar el código de usuario y generar
información o código basado en ese análisis que usan las tecnologías de hoy en día:
Por ejemplo, ASP.NET Core usa la reflexión cuando el servicio web se ejecuta por
primera vez para detectar las construcciones que ha definido para que pueda "conectar"
elementos, como controladores y Razor Pages. Aunque este escenario le permite escribir
código sencillo con abstracciones eficaces, viene acompañado de una penalización de
rendimiento en tiempo de ejecución: cuando el servicio web o la aplicación se inician
por primera vez, no pueden aceptar ninguna solicitud hasta que todo el código de
reflexión en tiempo de ejecución que descubre información sobre el código termine de
ejecutarse. Aunque esta penalización de rendimiento no es enorme, es un costo fijo que
no puede mejorar por sí mismo en su propia aplicación.
Otra funcionalidad que los generadores de código fuente pueden ofrecer es obviar el
uso de algunas API "fuertemente tipadas"; por ejemplo, el modo en que funciona el
enrutamiento de ASP.NET Core entre controladores y Razor Pages. Con un generador de
código fuente, el enrutamiento puede estar fuertemente tipado con la generación de las
cadenas necesarias como un detalle en tiempo de compilación. Esto reduciría la
cantidad de veces que un literal de cadena mal escrito genera una solicitud que no llega
al controlador correcto.
C#
namespace ConsoleApp;
HelloFrom("Generated Code");
7 Nota
Puede ejecutar este ejemplo tal y como está, pero todavía no ocurrirá nada.
XML
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp"
Version="4.4.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers"
Version="3.3.3">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>
Sugerencia
C#
using Microsoft.CodeAnalysis;
namespace SourceGenerator
[Generator]
C#
using Microsoft.CodeAnalysis;
namespace SourceGenerator
[Generator]
var mainMethod =
context.Compilation.GetEntryPoint(context.CancellationToken);
using System;
namespace {mainMethod.ContainingNamespace.ToDisplayString()}
{{
{{
}}
}}
";
context.AddSource($"{typeName}.g.cs", source);
Sugerencia
XML
<ItemGroup>
<ProjectReference Include="..\PathTo\SourceGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
Consola
7 Nota
Es posible que deba reiniciar Visual Studio para ver IntelliSense y deshacerse
de los errores a medida que se mejora activamente la experiencia con las
herramientas.
9. Si usa Visual Studio, puede ver los archivos generados del origen. En la ventana
Explorador de soluciones, expanda
Dependencias>Analizadores>SourceGenerator>SourceGenerator.HelloSourceGe
nerator y haga doble clic en el archivo Program.g.cs.
Pasos siguientes
En la guía paso a paso de los generadores de código fuente se abordan algunos de
estos ejemplos con algunos enfoques recomendados para resolverlos. Además, tenemos
un conjunto de ejemplos disponibles en GitHub que puede probar por su cuenta.
Puede aprender más sobre los generadores de código fuente en estos artículos:
En este tutorial, explorará la API de sintaxis. La API de sintaxis proporciona acceso a las
estructuras de datos que describen un programa de C# o Visual Basic. Estas estructuras
de datos tienen suficientes detalles para representar completamente un programa de
cualquier tamaño. Estas estructuras pueden describir programas completos que se
compilen y ejecuten correctamente. También pueden describir programas incompletos,
conforme los escribe, en el editor.
Para habilitar esta expresión completa, las estructuras de datos y las API que constituyen
la API de sintaxis son necesariamente complejas. Empecemos con el aspecto de la
estructura de datos para el programa típico “Hola mundo”:
C#
using System;
using System.Collections.Generic;
using System.Linq;
namespace HelloWorld
class Program
Console.WriteLine("Hello World!");
Mire el texto del programa anterior. Reconoce elementos conocidos. Todo el texto
representa un único archivo de código fuente o una unidad de compilación. Las tres
primeras líneas del archivo de código fuente son directivas using. El código fuente
restante se encuentra en una declaración de espacio de nombres. La declaración de
espacio de nombres contiene una declaración de clase secundaria. La declaración de
clase contiene una declaración de método.
Los cuatro pilares principales de los árboles de sintaxis son los siguientes:
La trivialidad, los tokens y los nodos se componen de forma jerárquica para formar un
árbol que representa por completo todo lo que hay en un fragmento de código de
Visual Basic o C#. Puede ver esta estructura mediante la ventana Syntax Visualizer
(Visualizador de sintaxis). En Visual Studio, elija Vista>Otras ventanas>Syntax Visualizer
(Visualizador de sintaxis). Por ejemplo, el archivo de código fuente de C# anterior
examinado con Syntax Visualizer (Visualizador de sintaxis) tiene el mismo aspecto que
en la siguiente ilustración:
SyntaxNode: Azul | SyntaxToken: Verde | SyntaxTrivia: Rojo
Aunque puede buscar cualquier elemento en un archivo de código mediante las API de
sintaxis, la mayoría de los escenarios implican examinar pequeños fragmentos de
código o buscar instrucciones o fragmentos concretos. Los dos ejemplos siguientes
muestran usos típicos para examinar la estructura del código o buscar instrucciones
únicas.
Recorrer árboles
Puede examinar los nodos de un árbol de sintaxis de dos maneras. Puede recorrer el
árbol para examinar cada nodo o puede consultar elementos o nodos concretos.
Recorrido manual
Puede ver el código terminado de este ejemplo en nuestro repositorio de GitHub .
7 Nota
Los tipos de árbol de sintaxis usan la herencia para describir los diferentes
elementos de sintaxis que son válidos en diferentes ubicaciones del programa. A
menudo, usar estas API significa convertir propiedades o miembros de colección en
tipos derivados concretos. En los ejemplos siguientes, la asignación y las
conversiones son instrucciones independientes, con variables con tipo explícito.
Puede leer el código para ver los tipos de valor devuelto de la API y el tipo de
motor de ejecución de los objetos devueltos. En la práctica, es más habitual usar
variables con tipo implícito y basarse en nombres de API para describir el tipo de
los objetos que se examinan.
C#
@"using System;
using System.Collections;
using System.Linq;
using System.Text;
namespace HelloWorld
class Program
Console.WriteLine(""Hello, World!"");
}";
A continuación, agregue el código siguiente para crear el árbol de sintaxis para el texto
del código de la constante programText . Agregue la línea siguiente al método Main :
C#
Estas dos líneas crean el árbol y recuperan su nodo raíz. Ahora puede examinar los
nodos del árbol. Agregue estas líneas al método Main para mostrar algunas de las
propiedades del nodo raíz en el árbol:
C#
WriteLine($"\t{element.Name}");
Ejecute la aplicación para ver lo que ha detectado el código sobre el nodo raíz de este
árbol.
C#
C#
WriteLine($"There are {helloWorldDeclaration.Members.Count} members declared
in this namespace.");
C#
var programDeclaration =
(ClassDeclarationSyntax)helloWorldDeclaration.Members[0];
var mainDeclaration =
(MethodDeclarationSyntax)programDeclaration.Members[0];
C#
WriteLine(mainDeclaration.Body.ToFullString());
Ejecute el programa para ver toda la información que ya conoce sobre este programa:
text
The tree is a CompilationUnit node.
System
System.Collections
System.Linq
System.Text
Console.WriteLine("Hello, World!");
Métodos de consulta
Además de recorrer árboles, también puede explorar el árbol de sintaxis mediante los
métodos de consulta definidos en Microsoft.CodeAnalysis.SyntaxNode. Cualquier
persona que conozca XPath debería conocer estos métodos. Puede usarlos con LINQ
para buscar elementos rápidamente en un árbol. SyntaxNode tiene métodos de consulta
como DescendantNodes, AncestorsAndSelf y ChildNodes.
Puede usar estos métodos de consulta para buscar el argumento para el método Main
como una alternativa a navegar por el árbol. Agregue el siguiente código en la parte
inferior del método Main :
C#
.OfType<MethodDeclarationSyntax>()
select
methodDeclaration.ParameterList.Parameters.First();
WriteLine(argsParameter == argsParameter2);
Rastreadores de sintaxis
A menudo, quiere buscar todos los nodos de un tipo concreto en un árbol de sintaxis,
por ejemplo, todas las declaraciones de propiedad de un archivo. Si extiende la clase
Microsoft.CodeAnalysis.CSharp.CSharpSyntaxWalker e invalida el método
VisitPropertyDeclaration(PropertyDeclarationSyntax), se procesan todas las
declaraciones de propiedad de un árbol de sintaxis sin conocer su estructura de
antemano. CSharpSyntaxWalker es un tipo determinado de CSharpSyntaxVisitor que
visita de forma recurrente un nodo y todos sus elementos secundarios.
Como en el ejemplo anterior, puede definir una constante de cadena para que contenga
el texto del programa que se va a analizar:
C#
@"using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
namespace TopLevel
using Microsoft;
using System.ComponentModel;
namespace Child1
using Microsoft.Win32;
using System.Runtime.InteropServices;
class Foo { }
namespace Child2
using System.CodeDom;
using Microsoft.CSharp;
class Bar { }
}";
Este texto de origen contiene directivas using dispersas por cuatro ubicaciones
diferentes: el nivel de archivo, en el espacio de nombres de nivel superior y en los dos
espacios de nombres anidados. En este ejemplo, se destaca un escenario principal para
usar la clase CSharpSyntaxWalker en el código de la consulta. Sería complejo visitar
todos los nodos del árbol de sintaxis raíz para buscar las declaraciones using. En su
lugar, cree una clase derivada y reemplace el método al que se llama solo cuando el
nodo actual del árbol sea una directiva using. El visitante no hace ningún trabajo en
ningún otro tipo de nodo. Este método único examina todas las instrucciones using y
compila una colección de los espacios de nombres que no están en el espacio de
nombres System . Compile un CSharpSyntaxWalker que examine todas las instrucciones
using , pero solo las instrucciones using .
Ahora que ha definido el texto del programa, debe crear un SyntaxTree y obtener la raíz
de ese árbol:
C#
C#
class UsingCollector : CSharpSyntaxWalker
Necesita almacenamiento para contener los nodos del espacio de nombres que está
recopilando. Declare una propiedad pública de solo lectura en la clase UsingCollector ;
use esta variable para almacenar los nodos UsingDirectiveSyntax que encuentre:
C#
La clase base, CSharpSyntaxWalker, implementa la lógica para visitar todos los nodos del
árbol de sintaxis. La clase derivada reemplaza los métodos llamados por los nodos
específicos que le interesan. En este caso, le interesa cualquier directiva using . Por
tanto, debe invalidar el método VisitUsingDirective(UsingDirectiveSyntax). El único
argumento de este método es un objeto
Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax. Se trata de una ventaja
importante de usar los visitantes: llaman a los métodos invalidados con argumentos que
ya se han convertido al tipo de nodo concreto. La clase
Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax tiene una propiedad Name
que almacena el nombre del espacio de nombres que se va a importar. Es una
Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax. Agregue el código siguiente en la
invalidación VisitUsingDirective(UsingDirectiveSyntax):
C#
!node.Name.ToString().StartsWith("System."))
this.Usings.Add(node);
Por último, debe agregar dos líneas de código para crear el UsingCollector y hacer que
visite el nodo raíz y recopile todas las instrucciones using . A continuación, agregue un
bucle foreach para que muestre todas las instrucciones using que encuentre el
recopilador:
C#
collector.Visit(root);
WriteLine(directive.Name);
Consola
Microsoft.CodeAnalysis
Microsoft.CodeAnalysis.CSharp
Microsoft
Microsoft.Win32
Microsoft.CSharp
En este tutorial, explorará las API de símbolo y enlace. Estas API ofrecen información
sobre el significado semántico de un programa. Le permiten formular y responder
preguntas sobre los tipos representados por cualquier símbolo en el programa.
7 Nota
Los tipos de árbol de sintaxis usan la herencia para describir los diferentes
elementos de sintaxis que son válidos en diferentes ubicaciones del programa. A
menudo, usar estas API significa convertir propiedades o miembros de colección en
tipos derivados concretos. En los ejemplos siguientes, la asignación y las
conversiones son instrucciones independientes, con variables con tipo explícito.
Puede leer el código para ver los tipos de valor devuelto de la API y el tipo de
motor de ejecución de los objetos devueltos. En la práctica, es más habitual usar
variables con tipo implícito y basarse en nombres de API para describir el tipo de
los objetos que se examinan.
C#
@"using System;
using System.Collections.Generic;
using System.Text;
namespace HelloWorld
class Program
Console.WriteLine(""Hello, World!"");
}";
A continuación, agregue el código siguiente para crear el árbol de sintaxis para el texto
del código de la constante programText . Agregue la línea siguiente al método Main :
C#
C#
.AddReferences(MetadataReference.CreateFromFile(
typeof(string).Assembly.Location))
.AddSyntaxTrees(tree);
C#
SemanticModel model = compilation.GetSemanticModel(tree);
Enlazar un nombre
La Compilation crea el SemanticModel desde el SyntaxTree. Después de crear el modelo,
puede consultarlo para buscar la primera directiva using y recuperar la información de
símbolo del espacio de nombres System . Agregue estas dos líneas en su método Main
para crear el modelo semántico y recuperar el símbolo de la primera instrucción using:
C#
System . El código anterior también muestra que usa el modelo de sintaxis para buscar
C#
Console.WriteLine(ns);
Resultados
System.Collections
System.Configuration
System.Deployment
System.Diagnostics
System.Globalization
System.IO
System.Numerics
System.Reflection
System.Resources
System.Runtime
System.Security
System.StubHelpers
System.Text
System.Threading
7 Nota
La salida no incluye todos los espacios de nombres que son secundarios del
espacio de nombres System . Muestra cada espacio de nombres que se encuentra
en esta compilación, que solo hace referencia al ensamblado donde se declara
System.String . Esta compilación no conoce ningún espacio de nombres declarado
en otros ensamblados.
C#
.OfType<LiteralExpressionSyntax>()
.Single();
C#
Para finalizar este tutorial, crearemos una consulta LINQ que crea una secuencia de
todos los métodos públicos declarados en el tipo string que devuelven una string .
Esta consulta es más compleja, así que la compilaremos línea a línea y, después, la
volveremos a construir como una única consulta. El origen de esta consulta es la
secuencia de todos los miembros declarados en el tipo string :
C#
Esa secuencia de origen contiene todos los miembros, incluidas las propiedades y los
campos, de modo que tiene que filtrarlos con el método ImmutableArray<T>.OfType
para buscar los elementos que son objetos Microsoft.CodeAnalysis.IMethodSymbol:
C#
A continuación, agregue otro filtro para devolver solo aquellos métodos que son
públicos y devuelven una string :
C#
var publicStringReturningMethods = methods
m.DeclaredAccessibility == Accessibility.Public);
Seleccione solo la propiedad name y solo los nombres distintos; para ello, elimine las
sobrecargas:
C#
C#
.GetMembers().OfType<IMethodSymbol>()
method.DeclaredAccessibility ==
Accessibility.Public
select method.Name).Distinct())
Console.WriteLine(name);
Resultados
Join
Substring
Trim
TrimStart
TrimEnd
Normalize
PadLeft
PadRight
ToLower
ToLowerInvariant
ToUpper
ToUpperInvariant
ToString
Insert
Replace
Remove
Format
Copy
Concat
Intern
IsInterned
Ha usado la API semántica para buscar y mostrar información sobre los símbolos que
forman parte de este programa.
Introducción a la transformación de
sintaxis
Artículo • 15/02/2023 • Tiempo de lectura: 13 minutos
En este tutorial rápido, se exploran las técnicas para crear y transformar árboles de
sintaxis. En combinación con las técnicas que aprendió en los tutoriales anteriores,
podrá crear la primera refactorización de línea de comandos.
Inicie Visual Studio y cree un proyecto de Stand-Alone Code Analysis Tool (Herramienta
de análisis de código independiente) de C#. En Visual Studio, elija
Archivo>Nuevo>Proyecto para mostrar el cuadro de diálogo Nuevo proyecto. En Visual
C#>Extensibilidad, elija Stand-Alone Code Analysis Tool (Herramienta de análisis de
código independiente). Este tutorial rápido tiene dos proyectos de ejemplo, así que
llame a la solución SyntaxTransformationQuickStart y al proyecto ConstructionCS.
Haga clic en Aceptar.
C#
Creará nodos de sintaxis de nombre para generar el árbol que representa la instrucción
using System.Collections.Generic; . NameSyntax es la clase base para los cuatro tipos
de nombres que aparecen en C#. Junte estos cuatro tipos de nombres para crear
cualquier nombre que pueda aparecer en el lenguaje C#:
C#
C#
WriteLine(name.ToString());
Vuelva a ejecutar el código y observe los resultados. Está creando un árbol de nodos
que representa código. Continuará con este patrón para compilar QualifiedNameSyntax
para el espacio de nombres System.Collections.Generic . Agrega el código siguiente a
Program.cs :
C#
WriteLine(name.ToString());
Ejecute el programa de nuevo para ver si ha creado el árbol para agregar el código.
C#
@"using System;
using System.Collections;
using System.Linq;
using System.Text;
namespace HelloWorld
class Program
Console.WriteLine(""Hello, World!"");
}";
7 Nota
C#
C#
WriteLine(root.ToString());
C#
WriteLine(root.ToString());
Ejecute el programa otra vez. Esta vez el árbol importa correctamente el espacio de
nombres System.Collections.Generic .
El primer paso es crear una clase que se derive de CSharpSyntaxRewriter para realizar las
transformaciones. Agregue un nuevo archivo de clase al proyecto. En Visual Studio, elija
Proyecto>Agregar clase... . En el cuadro de diálogo Agregar nuevo elemento, escriba
TypeInferenceRewriter.cs como nombre de archivo.
C#
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
C#
Agregue el código siguiente para declarar un campo privado de solo lectura para que
contenga un SemanticModel e inicializarlo en el constructor. Necesitará este campo más
adelante para determinar donde se puede usar la inferencia de tipos:
C#
C#
7 Nota
Muchas de las API de Roslyn declaran tipos de valor devuelto que son clases base
de los tipos en tiempo de ejecución reales devueltos. En muchos escenarios, un
tipo de nodo puede reemplazarse por otro tipo de nodo por completo, e incluso
eliminarse. En este ejemplo, el método
VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax) devuelve un
SyntaxNode, en vez del tipo derivado de LocalDeclarationStatementSyntax. Este
sistema de reescritura devuelve un nuevo nodo LocalDeclarationStatementSyntax
basado en el existente.
Este tutorial rápido controla las declaraciones de variable locales. Puede ampliarlo para
otras declaraciones como bucles foreach , bucles for , expresiones LINQ y expresiones
lambda. Además, este sistema de reescritura transformará solo las declaraciones de la
forma más sencilla:
C#
C#
variable2 = expression2;
// No initializer.
Type variable;
if (node.Declaration.Variables.Count > 1)
return node;
if (node.Declaration.Variables[0].Initializer == null)
return node;
C#
.GetSymbolInfo(variableTypeName)
.Symbol;
C#
var initializerInfo =
SemanticModel.GetTypeInfo(declarator.Initializer.Value);
C#
if (SymbolEqualityComparer.Default.Equals(variableType,
initializerInfo.Type))
.WithLeadingTrivia(variableTypeName.GetLeadingTrivia())
.WithTrailingTrivia(variableTypeName.GetTrailingTrivia());
else
return node;
C#
C#
if (newSource != sourceTree.GetRoot())
File.WriteAllText(sourceTree.FilePath, newSource.ToFullString());
C#
if (newSource != sourceTree.GetRoot())
File.WriteAllText(sourceTree.FilePath, newSource.ToFullString());
Casi ha terminado. Queda un último paso: crear una prueba Compilation. Puesto que no
ha utilizado ninguna inferencia de tipos durante este tutorial rápido, habría sido un caso
de prueba perfecto. Desafortunadamente, la creación de una compilación desde un
archivo de proyecto de C# queda fuera del ámbito de este tutorial. Pero
afortunadamente, si ha seguido las instrucciones con cuidado, hay esperanza.
Reemplace el contenido del método CreateTestCompilation con el código siguiente.
Crea una compilación de prueba que casualmente coincide con el proyecto descrito en
este tutorial rápido:
C#
SyntaxTree programTree =
CSharpSyntaxTree.ParseText(programText)
.WithFilePath(programPath);
SyntaxTree rewriterTree =
CSharpSyntaxTree.ParseText(rewriterText)
.WithFilePath(rewriterPath);
MetadataReference mscorlib =
MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
MetadataReference codeAnalysis =
MetadataReference.CreateFromFile(typeof(SyntaxTree).Assembly.Location);
MetadataReference csharpCodeAnalysis =
MetadataReference.CreateFromFile(typeof(CSharpSyntaxTree).Assembly.Location)
;
return CSharpCompilation.Create("TransformationCS",
sourceTrees,
references,
new CSharpCompilationOptions(OutputKind.ConsoleApplication));
El SDK de .NET Compiler Platform proporciona las herramientas que necesita para crear
diagnósticos personalizados (analizadores), correcciones de código, refactorización de
código y supresores de diagnóstico que tengan como destino código de C# o
Visual Basic. Un analizador contiene código que reconoce las infracciones de la regla. La
corrección del código contiene el código que corrige la infracción. Las reglas
implementadas pueden ser cualquier elemento de la estructura de código para codificar
el estilo de las convenciones de nomenclatura y mucho más. .NET Compiler Platform
proporciona el marco para ejecutar el análisis a medida que los desarrolladores escriben
código y todas las características de la interfaz de usuario de Visual Studio para corregir
código: mostrar líneas de subrayado en el editor, rellenar la lista de errores de Visual
Studio, crear las sugerencias con "bombillas" y mostrar la vista previa enriquecida de las
correcciones sugeridas.
Requisitos previos
Visual Studio 2019 , versión 16.8 o posterior
Debe instalar el SDK de .NET Compiler Platform a través del Instalador de Visual Studio:
1. Crear la solución.
2. Registrar el nombre del analizador y la descripción.
3. Notificar las recomendaciones y las advertencias del analizador.
4. Implementar la corrección de código para aceptar las recomendaciones.
5. Mejorar el análisis mediante las pruebas unitarias.
Creación de la solución
En Visual Studio, elija Archivo > Nuevo > Proyecto... para mostrar el cuadro de
diálogo Nuevo proyecto.
En Visual C# > Extensibilidad, elija Analizador con corrección de código (.NET
Standard).
Asigne al proyecto el nombre "MakeConst" y haga clic en Aceptar.
7 Nota
7 Nota
Sugerencia
Al ejecutar el analizador, inicie una segunda copia de Visual Studio. Esta segunda
copia usa un subárbol del registro diferente para almacenar la configuración. Le
permite diferenciar la configuración visual en las dos copias de Visual Studio. Puede
elegir un tema diferente para la ejecución experimental de Visual Studio. Además,
no mueva la configuración o el inicio de sesión a la cuenta de Visual Studio con la
ejecución experimental de Visual Studio. Así se mantiene una configuración
diferente.
La plantilla crea un analizador que notifica una advertencia en cada declaración de tipo,
donde el nombre de tipo contiene letras minúsculas, tal como se muestra en la
ilustración siguiente:
No tiene que iniciar una segunda copia de Visual Studio, y cree código para probar
todos los cambios en el analizador. La plantilla también crea un proyecto de prueba
unitaria de forma automática. Este proyecto contiene dos pruebas. TestMethod1 muestra
el formato típico de una prueba que analiza el código sin que se desencadene un
diagnóstico. TestMethod2 muestra el formato de una prueba que desencadena un
diagnóstico y, a continuación, se aplica una corrección de código sugerida. Al crear el
analizador y la corrección de código, deberá escribir pruebas para diferentes estructuras
de código para verificar el trabajo. Las pruebas unitarias de los analizadores son mucho
más rápidas que las pruebas interactivas con Visual Studio.
Sugerencia
Las pruebas unitarias del analizador son una herramienta magnífica si se sabe qué
construcciones de código deben y no deben desencadenar el analizador. Cargar el
analizador en otra copia de Visual Studio es una herramienta magnífica para
explorar y buscar construcciones en las que puede no haber pensado todavía.
C#
int x = 0;
Console.WriteLine(x);
C#
const int x = 0;
Console.WriteLine(x);
El análisis para determinar si una variable se puede convertir en constante, que requiere
un análisis sintáctico, un análisis constante de la expresión del inicializador y un análisis
del flujo de datos para garantizar que no se escriba nunca en la variable. .NET Compiler
Platform proporciona las API que facilita la realización de este análisis.
La plantilla también muestra las características básicas que forman parte de cualquier
analizador:
1. Registre acciones. Las acciones representan los cambios de código que deben
desencadenar el analizador para examinar el código para las infracciones. Cuando
Visual Studio detecta las modificaciones del código que coinciden con una acción
registrada, llama al método registrado del analizador.
2. Cree diagnósticos. Cuando el analizador detecta una infracción, crea un objeto de
diagnóstico que Visual Studio usa para notificar la infracción al usuario.
Cuando haya terminado, el editor de recursos debe aparecer tal y como se muestra en
la ilustración siguiente:
C#
context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);
C#
context.RegisterSyntaxNodeAction(AnalyzeNode,
SyntaxKind.LocalDeclarationStatement);
C#
C#
C#
int x = 0;
Console.WriteLine(x);
C#
C#
if (localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
return;
Por último, deberá comprobar que la variable podría ser const . Esto significa asegurarse
de que nunca se asigne después de inicializarse.
C#
DataFlowAnalysis dataFlowAnalysis =
context.SemanticModel.AnalyzeDataFlow(localDeclaration);
// Retrieve the local symbol for each variable in the local declaration
// and ensure that it is not written outside of the data flow analysis
region.
VariableDeclaratorSyntax variable =
localDeclaration.Declaration.Variables.Single();
if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
return;
C#
context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation(),
localDeclaration.Declaration.Variables.First().Identifier.ValueText));
C#
int x = 0;
Console.WriteLine(x);
diff
- int x = 0;
+ const int x = 0;
Console.WriteLine(x);
C#
var declaration =
root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<LocalD
eclarationStatementSyntax>().First();
C#
context.RegisterCodeFix(
CodeAction.Create(
title: CodeFixResources.CodeFixTitle,
equivalenceKey: nameof(CodeFixResources.CodeFixTitle)),
diagnostic);
C#
LocalDeclarationStatementSyntax localDeclaration,
CancellationToken cancellationToken)
C#
LocalDeclarationStatementSyntax trimmedLocal =
localDeclaration.ReplaceToken(
firstToken, firstToken.WithLeadingTrivia(SyntaxTriviaList.Empty));
C#
// Insert the const token into the modifiers list, creating a new modifiers
list.
.WithModifiers(newModifiers)
.WithDeclaration(localDeclaration.Declaration);
Después aplique formato a la nueva declaración para que coincida con las reglas de
formato de C#. Aplicar formato a los cambios para que coincidan con el código
existente mejora la experiencia. Agregue la instrucción siguiente inmediatamente
después del código existente:
C#
LocalDeclarationStatementSyntax formattedLocal =
newLocal.WithAdditionalAnnotations(Formatter.Annotation);
C#
using Microsoft.CodeAnalysis.Formatting;
El último paso es realizar la edición. Hay tres pasos para este proceso:
C#
// Replace the old local declaration with the new local declaration.
return document.WithSyntaxRoot(newRoot);
La corrección de código está lista para probarla. Presione F5 para ejecutar el proyecto
del analizador en una segunda instancia de Visual Studio. En la segunda instancia de
Visual Studio, cree un proyecto de aplicación de consola de C# y agregue algunas
declaraciones de variable local inicializadas con valores de constante para el método
Main. Observará que se notifican como advertencias de la siguiente forma.
Sugerencia
C#
[TestMethod]
await VerifyCS.VerifyCodeFixAsync(@"
using System;
class Program
[|int i = 0;|]
Console.WriteLine(i);
", @"
using System;
class Program
const int i = 0;
Console.WriteLine(i);
");
Ejecute esta prueba para asegurarse de que se supera. En Visual Studio, abra el
Explorador de pruebas; para ello, seleccione Prueba>Windows>Explorador de
pruebas. Luego, seleccione Ejecutar todo.
C#
[TestMethod]
await VerifyCS.VerifyAnalyzerAsync(@"
using System;
class Program
int i = 0;
Console.WriteLine(i++);
");
Esta prueba también pasa. Después, agregue métodos de prueba para las condiciones
que todavía no ha controlado:
C#
[TestMethod]
await VerifyCS.VerifyAnalyzerAsync(@"
using System;
class Program
const int i = 0;
Console.WriteLine(i);
");
Declaraciones que no tienen inicializador, porque no hay ningún valor para usar:
C#
[TestMethod]
await VerifyCS.VerifyAnalyzerAsync(@"
using System;
class Program
int i;
i = 0;
Console.WriteLine(i);
");
C#
[TestMethod]
await VerifyCS.VerifyAnalyzerAsync(@"
using System;
class Program
int i = DateTime.Now.DayOfYear;
Console.WriteLine(i);
");
Puede ser incluso más complicado, porque C# admite varias declaraciones como una
instrucción. Considere la siguiente constante de cadena de caso de prueba:
C#
[TestMethod]
await VerifyCS.VerifyAnalyzerAsync(@"
using System;
class Program
int i = 0, j = DateTime.Now.DayOfYear;
Console.WriteLine(i);
Console.WriteLine(j);
");
Vuelva a ejecutar las pruebas y, después, observará que estos nuevos casos de prueba
generarán errores.
C#
DataFlowAnalysis dataFlowAnalysis =
context.SemanticModel.AnalyzeDataFlow(localDeclaration);
// Retrieve the local symbol for each variable in the local declaration
// and ensure that it is not written outside of the data flow analysis
region.
VariableDeclaratorSyntax variable =
localDeclaration.Declaration.Variables.Single();
if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
return;
C#
// Ensure that all variables in the local declaration have initializers that
if (initializer == null)
return;
Optional<object> constantValue =
context.SemanticModel.GetConstantValue(initializer.Value,
context.CancellationToken);
if (!constantValue.HasValue)
return;
DataFlowAnalysis dataFlowAnalysis =
context.SemanticModel.AnalyzeDataFlow(localDeclaration);
// Retrieve the local symbol for each variable in the local declaration
// and ensure that it is not written outside of the data flow analysis
region.
ISymbol variableSymbol =
context.SemanticModel.GetDeclaredSymbol(variable,
context.CancellationToken);
if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
return;
El primer bucle foreach examina cada declaración de variable con análisis sintácticos La
primera comprobación garantiza que la variable tiene un inicializador. La segunda
comprobación garantiza que el inicializador es una constante. El segundo bucle tiene el
análisis semántico original. Las comprobaciones semánticas se encuentran en un bucle
independiente porque afectan más al rendimiento. Vuelva a ejecutar las pruebas y
observará que todas pasan.
C#
[TestMethod]
await VerifyCS.VerifyAnalyzerAsync(@"
using System;
class Program
int x = {|CS0029:""abc""|};
");
[TestMethod]
await VerifyCS.VerifyAnalyzerAsync(@"
using System;
class Program
object s = ""abc"";
");
Para ser exhaustivo, debe agregar otra prueba para asegurarse de que puede crear una
declaración de constante para una cadena. El fragmento de código siguiente define el
código que genera el diagnóstico y el código después de haber aplicado la corrección:
C#
[TestMethod]
await VerifyCS.VerifyCodeFixAsync(@"
using System;
class Program
[|string s = ""abc"";|]
", @"
using System;
class Program
");
Por último, si una variable se declara con la palabra clave var , la corrección de código
hace una función incorrecta y genera una declaración const var , que el lenguaje C# no
admite. Para corregir este error, la corrección de código debe reemplazar la palabra
clave var por el nombre del tipo deducido:
C#
[TestMethod]
await VerifyCS.VerifyCodeFixAsync(@"
using System;
class Program
", @"
using System;
class Program
");
[TestMethod]
await VerifyCS.VerifyCodeFixAsync(@"
using System;
class Program
", @"
using System;
class Program
");
Afortunadamente, todos los errores anteriores se pueden tratar con las mismas técnicas
que acaba de aprender.
C#
ITypeSymbol variableType =
context.SemanticModel.GetTypeInfo(variableTypeName,
context.CancellationToken).ConvertedType;
Después, dentro del bucle foreach , compruebe cada inicializador para asegurarse de
que se puede convertir al tipo de variable. Agregue la siguiente comprobación después
de asegurarse de que el inicializador es una constante:
C#
// Ensure that the initializer value can be converted to the type of the
Conversion conversion =
context.SemanticModel.ClassifyConversion(initializer.Value, variableType);
if (!conversion.Exists || conversion.IsUserDefined)
return;
El siguiente cambio se basa en el último. Antes de cerrar la llave del primer bucle
foreach, agregue el código siguiente para comprobar el tipo de declaración local
cuando la constante es una cadena o null.
C#
// Special cases:
// must be System.String.
// * If the constant value is null, the type of the local declaration must
// be a reference type.
if (constantValue.Value is string)
if (variableType.SpecialType != SpecialType.System_String)
return;
return;
Asegúrese de que var no es un nombre de tipo en este programa. (Si es así, const
var es válido).
Simplificación del nombre de tipo completo
Parece mucho código. Pero no lo es. Reemplace la línea que declara e inicializa newLocal
con el código siguiente. Va inmediatamente después de la inicialización de
newModifiers :
C#
VariableDeclarationSyntax variableDeclaration =
localDeclaration.Declaration;
if (variableTypeName.IsVar)
if (aliasInfo == null)
if (type.Name != "var")
// to keep any leading and trailing trivia from the var keyword.
TypeSyntax typeName =
SyntaxFactory.ParseTypeName(type.ToDisplayString())
.WithLeadingTrivia(variableTypeName.GetLeadingTrivia())
.WithTrailingTrivia(variableTypeName.GetTrailingTrivia());
TypeSyntax simplifiedTypeName =
typeName.WithAdditionalAnnotations(Simplifier.Annotation);
variableDeclaration =
variableDeclaration.WithType(simplifiedTypeName);
LocalDeclarationStatementSyntax newLocal =
trimmedLocal.WithModifiers(newModifiers)
.WithDeclaration(variableDeclaration);
C#
using Microsoft.CodeAnalysis.Simplification;
Ejecute las pruebas, y todas deberían pasar. Felicítese por ejecutar el analizador
terminado. Presione Ctrl + F5 para ejecutar el proyecto de analizador en una segunda
instancia de Visual Studio con la extensión de la versión preliminar de Roslyn cargada.
C#
int i = 2;
int j = 32;
int k = i + j;
Después de estos cambios, obtendrá un subrayado ondulado rojo solo en las dos
primeras variables. Agregue const a i y j , y obtendrá una nueva advertencia sobre k
porque ahora puede ser const .
Otros recursos
Introducción al análisis de sintaxis
Introducción al análisis semántico
Guía de programación de C#
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
En la mayor parte de esta sección se supone que ya sabe algo sobre C# y que conoce
los conceptos de programación generales. Si nunca ha programado ni ha trabajado con
C#, puede consultar los tutoriales de introducción a C# o el tutorial de .NET en
explorador , que no requieren ningún conocimiento previo de programación.
Secciones de programa
Dentro de un programa de C#
Secciones de lenguaje
InstruccionesOperadores y expresionesMiembros con cuerpo de
expresiónComparaciones de igualdad
Tipos
Interfaces
Delegados
Matrices
Cadenas
Propiedades
Indizadores
Eventos
Genéricos
Iteradores
Espacios de nombres
Secciones de la plataforma
Dominios de aplicación
Ensamblados de .NET
Atributos
Colecciones
Interoperabilidad
Reflexión
Vea también
Referencia de C#
Conceptos de programación (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
En esta sección
Title Descripción
Programación Describe cómo escribir soluciones asincrónicas mediante las palabras clave
asincrónica con Async y Await en C#. Incluye un tutorial.
Async y Await
(C#)
Colecciones Describe algunos de los tipos de colecciones proporcionadas por .NET. Muestra
(C#) cómo usar colecciones sencillas y colecciones de pares clave-valor.
Árboles de Explica cómo puede utilizar árboles de expresión para habilitar la modificación
expresión (C#) dinámica de código ejecutable.
Iteradores (C#) Describe los iteradores, que se usan para recorrer colecciones y devolver los
elementos uno a uno.
Language Se describen las eficaces funciones de consulta de la sintaxis del lenguaje C#,
Integrated así como el modelo para consultar bases de datos relacionales, documentos
Query (LINQ) XML, conjuntos de datos y colecciones en memoria.
(C#)
Reflexión (C#) Se explica cómo usar la reflexión para crear dinámicamente una instancia de un
tipo, enlazar el tipo a un objeto existente u obtener el tipo desde un objeto
existente e invocar sus métodos, o acceder a sus campos y propiedades.
Si tiene cualquier necesidad enlazada a E/S (por ejemplo, solicitar datos de una red,
acceder a una base de datos o leer y escribir un sistema de archivos), deberá usar la
programación asincrónica. También podría tener código enlazado a la CPU, como
realizar un cálculo costoso, que también es un buen escenario para escribir código
asincrónico.
Para el código enlazado a E/S, espera una operación que devuelva Task o Task<T>
dentro de un método async .
Para el código enlazado a la CPU, espera una operación que se inicia en un
subproceso en segundo plano con el método Task.Run.
La palabra clave await es donde ocurre la magia. Genera control para el autor de la
llamada del método que ha realizado await , y permite en última instancia una interfaz
de usuario con capacidad de respuesta o un servicio flexible. Aunque existen maneras
de abordar el código asincrónico diferentes de async y await , este artículo se centra en
las construcciones de nivel de lenguaje.
C#
private readonly HttpClient _httpClient = new HttpClient();
//
DoSomethingWithData(stringData);
};
C#
// Code omitted:
//
DisplayDamage(damageResult);
};
Este código expresa claramente la intención del evento de clic del botón, no requiere la
administración manual de un subproceso en segundo plano y lo hace en un modo sin
bloqueo.
Para los más interesados en la teoría, se trata de una implementación del modelo de
promesas de asincronía .
A continuación, se indican dos preguntas que debe hacerse antes de escribir el código:
Si el trabajo que tiene está enlazado a E/S, use async y await sin Task.Run . No debe usar
la Biblioteca TPL.
Además, siempre debe medir la ejecución del código. Por ejemplo, puede verse en una
situación en la que el trabajo enlazado a la CPU no sea suficientemente costoso en
comparación con la sobrecarga de cambios de contexto cuando realice multithreading.
Cada opción tiene su compensación y debe elegir el equilibrio correcto para su
situación.
Más ejemplos
En los ejemplos siguientes se muestran distintas maneras en las que puede escribir
código asincrónico en C#. Abarcan algunos escenarios diferentes con los que puede
encontrarse.
7 Nota
C#
[HttpGet, Route("DotNetCount")]
Este es el mismo escenario escrito para una aplicación Windows Universal, que realiza la
misma tarea cuando se presiona un botón:
C#
// Capture the task handle here so we can await the background task
later.
var getDotNetFoundationHtmlTask =
_httpClient.GetStringAsync("https://dotnetfoundation.org");
// Any other work on the UI thread can be done here, such as enabling a
Progress Bar.
NetworkProgressBar.IsEnabled = true;
NetworkProgressBar.Visibility = Visibility.Visible;
// This is what allows the app to be responsive and not block the UI
thread.
NetworkProgressBar.IsEnabled = false;
NetworkProgressBar.Visibility = Visibility.Collapsed;
C#
// Code omitted:
//
getUserTasks.Add(GetUserAsync(userId));
Aquí tiene otra manera de escribir lo mismo de una forma más sucinta, con LINQ:
C#
// Code omitted:
//
Aunque es menos código, tenga cuidado al combinar LINQ con código asincrónico.
Dado que LINQ usa la ejecución diferida, las llamadas asincrónicas no se realizarán
inmediatamente, como lo hacen en un bucle foreach , a menos que fuerce la secuencia
generada a procesar una iteración con una llamada a .ToList() o .ToArray() . En el
ejemplo anterior se usa Enumerable.ToArray para realizar la consulta diligentemente y
almacenar los resultados en una matriz. Esto obliga al código id => GetUserAsync(id) a
ejecutar e iniciar la tarea.
Los métodos async deben tener una palabra clave await en el cuerpo o nunca
proporcionarán resultados.
Agregue "Async" como el sufijo de todos los métodos asincrónicos que escriba.
Se trata de la convención que se usa en .NET para distinguir más fácilmente los
métodos sincrónicos de los asincrónicos. No se aplican necesariamente ciertos
métodos a los que el código no llame explícitamente (como controladores de
eventos o métodos de controlador web). Puesto que el código no los llama
explícitamente, resulta importante explicitar sus nombres.
asincrónicos trabajar, ya que los eventos no tienen tipos de valor devuelto (por lo
tanto, no pueden hacer uso de Task y Task<T> ). Cualquier otro uso de async void
no sigue el modelo de TAP y puede resultar difícil de usar, como:
Las excepciones producidas en un método async void no se pueden detectar
fuera de ese método.
Los métodos async void resultan muy difíciles de probar.
Los métodos async void pueden provocar efectos secundarios negativos si el
autor de la llamada no espera que sean asincrónicos.
Bloquear el subproceso actual como un medio para esperar que se complete Task
puede dar lugar a interbloqueos y subprocesos de contexto bloqueados, y puede
requerir un control de errores más complejo. En la tabla siguiente se ofrece
orientación sobre cómo abordar la espera de las tareas de una manera que no
produzca un bloqueo:
Otros recursos
Modelo de programación asincrónica de tareas (C#).
Asynchronous programming with async
and await
Artículo • 13/02/2023 • Tiempo de lectura: 16 minutos
That's the goal of this syntax: enable code that reads like a sequence of statements, but
executes in a much more complicated order based on external resource allocation and
when tasks are complete. It's analogous to how people give instructions for processes
that include asynchronous tasks. Throughout this article, you'll use an example of
instructions for making breakfast to see how the async and await keywords make it
easier to reason about code that includes a series of asynchronous instructions. You'd
write the instructions something like the following list to explain how to make a
breakfast:
If you have experience with cooking, you'd execute those instructions asynchronously.
You'd start warming the pan for eggs, then start the bacon. You'd put the bread in the
toaster, then start the eggs. At each step of the process, you'd start a task, then turn
your attention to tasks that are ready for your attention.
Cooking breakfast is a good example of asynchronous work that isn't parallel. One
person (or thread) can handle all these tasks. Continuing the breakfast analogy, one
person can make breakfast asynchronously by starting the next task before the first task
completes. The cooking progresses whether or not someone is watching it. As soon as
you start warming the pan for the eggs, you can begin frying the bacon. Once the bacon
starts, you can put the bread into the toaster.
For a parallel algorithm, you'd need multiple cooks (or threads). One would make the
eggs, one the bacon, and so on. Each one would be focused on just that one task. Each
cook (or thread) would be blocked synchronously waiting for the bacon to be ready to
flip, or the toast to pop.
C#
using System;
using System.Threading.Tasks;
namespace AsyncBreakfast
class Program
Console.WriteLine("coffee is ready");
Console.WriteLine("bacon is ready");
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("toast is ready");
Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
Console.WriteLine("Start toasting...");
Task.Delay(3000).Wait();
Task.Delay(3000).Wait();
Task.Delay(3000).Wait();
Task.Delay(3000).Wait();
Task.Delay(3000).Wait();
Console.WriteLine("Pouring coffee");
The synchronously prepared breakfast took roughly 30 minutes because the total is the
sum of each task.
Computers don't interpret those instructions the same way people do. The computer
will block on each statement until the work is complete before moving on to the next
statement. That creates an unsatisfying breakfast. The later tasks wouldn't be started
until the earlier tasks had been completed. It would take much longer to create the
breakfast, and some items would have gotten cold before being served.
If you want the computer to execute the above instructions asynchronously, you must
write asynchronous code.
These concerns are important for the programs you write today. When you write client
programs, you want the UI to be responsive to user input. Your application shouldn't
make a phone appear frozen while it's downloading data from the web. When you write
server programs, you don't want threads blocked. Those threads could be serving other
requests. Using synchronous code when asynchronous alternatives exist hurts your
ability to scale out less expensively. You pay for those blocked threads.
Let's start by updating this code so that the thread doesn't block while tasks are
running. The await keyword provides a non-blocking way to start a task, then continue
execution when that task completes. A simple asynchronous version of the make a
breakfast code would look like the following snippet:
C#
Console.WriteLine("coffee is ready");
Console.WriteLine("bacon is ready");
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("toast is ready");
Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
) Importante
The total elapsed time is roughly the same as the initial synchronous version. The
code has yet to take advantage of some of the key features of asynchronous
programming.
Sugerencia
7 Nota
The Main method returns Task , despite not having a return expression—this is by
design. For more information, see Evaluation of a void-returning async function.
This code doesn't block while the eggs or the bacon are cooking. This code won't start
any other tasks though. You'd still put the toast in the toaster and stare at it until it pops.
But at least, you'd respond to anyone that wanted your attention. In a restaurant where
multiple orders are placed, the cook could start another breakfast while the first is
cooking.
Now, the thread working on the breakfast isn't blocked while awaiting any started task
that hasn't yet finished. For some applications, this change is all that's needed. A GUI
application still responds to the user with just this change. However, for this scenario,
you want more. You don't want each of the component tasks to be executed
sequentially. It's better to start each of the component tasks before awaiting the
previous task's completion.
The System.Threading.Tasks.Task and related types are classes you can use to reason
about tasks that are in progress. That enables you to write code that more closely
resembles the way you'd create breakfast. You'd start cooking the eggs, bacon, and
toast at the same time. As each requires action, you'd turn your attention to that task,
take care of the next action, then wait for something else that requires your attention.
You start a task and hold on to the Task object that represents the work. You'll await
each task before working with its result.
Let's make these changes to the breakfast code. The first step is to store the tasks for
operations when they start, rather than awaiting them:
C#
Console.WriteLine("Coffee is ready");
Console.WriteLine("Bacon is ready");
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("Toast is ready");
Juice oj = PourOJ();
Console.WriteLine("Oj is ready");
Console.WriteLine("Breakfast is ready!");
The preceding code won't get your breakfast ready any faster. The tasks are all await ed
as soon as they are started. Next, you can move the await statements for the bacon and
eggs to the end of the method, before serving breakfast:
C#
Console.WriteLine("Coffee is ready");
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("Toast is ready");
Juice oj = PourOJ();
Console.WriteLine("Oj is ready");
Console.WriteLine("Bacon is ready");
Console.WriteLine("Breakfast is ready!");
The asynchronously prepared breakfast took roughly 20 minutes, this time savings is
because some tasks ran concurrently.
The preceding code works better. You start all the asynchronous tasks at once. You await
each task only when you need the results. The preceding code may be similar to code in
a web application that makes requests to different microservices, then combines the
results into a single page. You'll make all the requests immediately, then await all those
tasks and compose the web page.
) Importante
C#
ApplyButter(toast);
ApplyJam(toast);
return toast;
The preceding method has the async modifier in its signature. That signals to the
compiler that this method contains an await statement; it contains asynchronous
operations. This method represents the task that toasts the bread, then adds butter and
jam. This method returns a Task<TResult> that represents the composition of those
three operations. The main block of code now becomes:
C#
Console.WriteLine("coffee is ready");
Console.WriteLine("bacon is ready");
Console.WriteLine("toast is ready");
Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
The previous change illustrated an important technique for working with asynchronous
code. You compose tasks by separating the operations into a new method that returns a
task. You can choose when to await that task. You can start other tasks concurrently.
Asynchronous exceptions
Up to this point, you've implicitly assumed that all these tasks complete successfully.
Asynchronous methods throw exceptions, just like their synchronous counterparts.
Asynchronous support for exceptions and error handling strives for the same goals as
asynchronous support in general: You should write code that reads like a series of
synchronous statements. Tasks throw exceptions when they can't complete successfully.
The client code can catch those exceptions when a started task is awaited . For example,
let's assume that the toaster catches fire while making the toast. You can simulate that
by modifying the ToastBreadAsync method to match the following code:
C#
Console.WriteLine("Start toasting...");
await Task.Delay(2000);
await Task.Delay(1000);
7 Nota
You'll get a warning when you compile the preceding code regarding unreachable
code. That's intentional, because once the toaster catches fire, operations won't
proceed normally.
Run the application after making these changes, and you'll output similar to the
following text:
Consola
Pouring coffee
Coffee is ready
Start toasting...
Cracking 2 eggs
Bacon is ready
at AsyncBreakfast.Program.ToastBreadAsync(Int32 slices) in
Program.cs:line 65
at AsyncBreakfast.Program.MakeToastWithButterAndJamAsync(Int32 number) in
Program.cs:line 36
at AsyncBreakfast.Program.<Main>(String[] args)
You'll notice quite a few tasks are completed between when the toaster catches fire and
the exception is observed. When a task that runs asynchronously throws an exception,
that Task is faulted. The Task object holds the exception thrown in the Task.Exception
property. Faulted tasks throw an exception when they're awaited.
When code running asynchronously throws an exception, that exception is stored in the
Task . The Task.Exception property is a System.AggregateException because more than
one exception may be thrown during asynchronous work. Any exception thrown is
added to the AggregateException.InnerExceptions collection. If that Exception property
is null, a new AggregateException is created and the thrown exception is the first item in
the collection.
The most common scenario for a faulted task is that the Exception property contains
exactly one exception. When code awaits a faulted task, the first exception in the
AggregateException.InnerExceptions collection is rethrown. That's why the output from
this example shows an InvalidOperationException instead of an AggregateException .
Extracting the first inner exception makes working with asynchronous methods as
similar as possible to working with their synchronous counterparts. You can examine the
Exception property in your code when your scenario may generate multiple exceptions.
Before going on, comment out these two lines in your ToastBreadAsync method. You
don't want to start another fire:
C#
C#
Console.WriteLine("Bacon is ready");
Console.WriteLine("Toast is ready");
Console.WriteLine("Breakfast is ready!");
Another option is to use WhenAny, which returns a Task<Task> that completes when
any of its arguments complete. You can await the returned task, knowing that it has
already finished. The following code shows how you could use WhenAny to await the
first task to finish and then process its result. After processing the result from the
completed task, you remove that completed task from the list of tasks passed to
WhenAny .
C#
if (finishedTask == eggsTask)
Console.WriteLine("Bacon is ready");
Console.WriteLine("Toast is ready");
await finishedTask;
breakfastTasks.Remove(finishedTask);
Near the end, you see the line await finishedTask; . The line await Task.WhenAny
doesn't await the finished task. It await s the Task returned by Task.WhenAny . The result
of Task.WhenAny is the task that has completed (or faulted). You should await that task
again, even though you know it's finished running. That's how you retrieve its result, or
ensure that the exception causing it to fault gets thrown.
After all those changes, the final version of the code looks like this:
C#
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace AsyncBreakfast
class Program
Console.WriteLine("coffee is ready");
if (finishedTask == eggsTask)
Console.WriteLine("bacon is ready");
Console.WriteLine("toast is ready");
await finishedTask;
breakfastTasks.Remove(finishedTask);
Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
}
ApplyButter(toast);
ApplyJam(toast);
return toast;
Console.WriteLine("Start toasting...");
await Task.Delay(3000);
await Task.Delay(3000);
await Task.Delay(3000);
await Task.Delay(3000);
await Task.Delay(3000);
Console.WriteLine("Pouring coffee");
The final version of the asynchronously prepared breakfast took roughly 15 minutes
because some tasks ran concurrently, and the code monitored multiple tasks at once
and only took action when it was needed.
This final code is asynchronous. It more accurately reflects how a person would cook a
breakfast. Compare the preceding code with the first code sample in this article. The
core actions are still clear from reading the code. You can read this code the same way
you'd read those instructions for making a breakfast at the beginning of this article. The
language features for async and await provide the translation every person makes to
follow those written instructions: start tasks as you can and don't block waiting for tasks
to complete.
Next steps
Explore real world scenarios for asynchronous programs
Modelo de programación asincrónica de
tareas
Artículo • 20/02/2023 • Tiempo de lectura: 14 minutos
Área de Tipos de .NET con métodos Tipos de Windows Runtime con métodos
aplicación asincrónicos asincrónicos
SyndicationClient
StreamWriter
XmlReader
XmlWriter
Área de Tipos de .NET con métodos Tipos de Windows Runtime con métodos
aplicación asincrónicos asincrónicos
imágenes BitmapEncoder
BitmapDecoder
C#
public async Task<int> GetUrlContentLengthAsync()
Task<string> getStringTask =
client.GetStringAsync("https://docs.microsoft.com/dotnet");
DoIndependentWork();
return contents.Length;
void DoIndependentWork()
Console.WriteLine("Working...");
getStringTask .
Mientras tanto, el control vuelve al autor de la llamada de
GetUrlContentLengthAsync .
Aquí se reanuda el control cuando se completa getStringTask .
Después, el operador await recupera el resultado string de getStringTask .
La instrucción return especifica un resultado entero. Los métodos que están a la espera
de GetUrlContentLengthAsync recuperan el valor de longitud.
C#
string contents = await
client.GetStringAsync("https://learn.microsoft.com/dotnet");
Las siguientes características resumen lo que hace que el ejemplo anterior sea un
método asincrónico:
En métodos asincrónicos, se utilizan las palabras clave y los tipos proporcionados para
indicar lo que se desea hacer y el compilador realiza el resto, incluido el seguimiento de
qué debe ocurrir cuando el control vuelve a un punto de espera en un método
suspendido. Algunos procesos de rutina, tales como bucles y control de excepciones,
pueden ser difíciles de controlar en código asincrónico tradicional. En un método
asincrónico, se pueden escribir estos elementos como se haría en una solución
sincrónica y se resuelve este problema.
Los números del diagrama se corresponden con los pasos siguientes, que se inician
cuando un método de llamada llama al método asincrónico.
llamador. La tarea representa una sugerencia para generar un resultado entero que
es la longitud de la cadena descargada.
7 Nota
Windows Runtime también contiene muchos métodos que puede utilizar con async y
await en Aplicaciones Windows. Para más información, consulte Subprocesamiento y
Subprocesos
La intención de los métodos Async es ser aplicaciones que no pueden producir
bloqueos. Una expresión await en un método asincrónico no bloquea el subproceso
actual mientras la tarea esperada se encuentra en ejecución. En vez de ello, la expresión
declara el resto del método como una continuación y devuelve el control al llamador del
método asincrónico.
Las palabras clave async y await no hacen que se creen subprocesos adicionales. Los
métodos Async no requieren multithreading, ya que un método asincrónico no se
ejecuta en su propio subproceso. El método se ejecuta en el contexto de sincronización
actual y ocupa tiempo en el subproceso únicamente cuando el método está activo.
Puede utilizar Task.Run para mover el trabajo enlazado a la CPU a un subproceso en
segundo plano, pero un subproceso en segundo plano no ayuda con un proceso que
solo está esperando a que los resultados estén disponibles.
El enfoque basado en asincrónico en la programación asincrónica es preferible a los
enfoques existentes en casi todos los casos. En concreto, este enfoque es mejor que la
clase BackgroundWorker para las operaciones enlazadas a E/S porque el código es más
sencillo y no se tiene que proteger contra las condiciones de carrera. Junto con el
método Task.Run, la programación asincrónica es mejor que BackgroundWorker para las
operaciones enlazadas a la CPU, ya que la programación asincrónica separa los detalles
de coordinación en la ejecución del código del trabajo que Task.Run transfiere al grupo
de subprocesos.
async y await
Si especifica que un método es un método asincrónico mediante el modificador async,
habilita las dos funciones siguientes.
async y await son palabras clave contextuales. Para mayor información y ejemplos, vea
los siguientes temas:
async
await
Puede utilizar Task como tipo de valor devuelto si el método no tiene instrucción return
o tiene una instrucción return que no devuelve un operando.
También puede especificar cualquier otro tipo de valor devuelto, siempre que dicho tipo
incluya un método GetAwaiter . Un ejemplo de este tipo es ValueTask<TResult>. Está
disponible en el paquete NuGet System.Threading.Tasks.Extension .
C#
int hours = 0;
await Task.Delay(0);
return hours;
// Single line
await Task.Delay(0);
await returnedTask;
// Single line
await GetTaskAsync();
Cada tarea devuelta representa el trabajo en curso. Una tarea encapsula la información
sobre el estado del proceso asincrónico y, finalmente, el resultado final del proceso o la
excepción que el proceso provoca si no tiene éxito.
Un método asincrónico también puede tener un tipo de valor devuelto void . Este tipo
de valor devuelto se utiliza principalmente para definir controladores de eventos, donde
se requiere un tipo de valor devuelto void . Los controladores de eventos asincrónicos
sirven a menudo como punto de partida para programas asincrónicos.
No se puede esperar a un método asincrónico que tenga un tipo de valor devuelto void
y el llamador de un método con tipo de valor devuelto void no puede capturar ninguna
excepción producida por este.
Un método asincrónico no puede declarar ningún parámetro in, ref o out, pero el
método puede llamar a los métodos que tienen estos parámetros. De forma similar, un
método asincrónico no puede devolver un valor por referencia, aunque puede llamar a
métodos con valores devueltos ref.
Para obtener más información y ejemplos, vea Tipos de valor devueltos asincrónicos
(C#). Para más información sobre cómo capturar excepciones en métodos asincrónicos,
vea try-catch.
Convención de nomenclatura
Por convención, los nombres de los métodos que devuelven tipos que suelen admitir
"await" (por ejemplo, Task , Task<T> , ValueTask y ValueTask<T> ) deben terminar por
"Async". Los nombres de los métodos que inician operaciones asincrónicas, pero que no
devuelven un tipo que admite "await", no deben terminar en "Async", pero pueden
empezar por "Begin", "Start" o cualquier otro verbo para sugerir que este método no
devuelve ni genera el resultado de la operación.
Puede ignorar esta convención cuando un evento, clase base o contrato de interfaz
sugieren un nombre diferente. Por ejemplo, no se debería cambiar el nombre de los
controladores de eventos, tales como OnButtonClick .
Procedimiento para realizar varias Demuestra cómo comenzar varias tareas al mismo
solicitudes web en paralelo con async y tiempo.
await (C#)
Tipos de valor devueltos asincrónicos Muestra los tipos que los métodos asincrónicos
(C#) pueden devolver y explica cuándo es apropiado cada
uno de ellos.
señalización.
- Cancelación de una lista de tareas (C#)
Usar Async en acceso a archivos (C#) Enumera y demuestra los beneficios de usar async y
await para obtener acceso a archivos.
Vea también
Programación asincrónica con async y await
async
await
Tipos de valor devueltos asincrónicos
(C#)
Artículo • 14/02/2023 • Tiempo de lectura: 10 minutos
Los métodos asincrónicos pueden tener los siguientes tipos de valor devuelto:
Task, para un método asincrónico que realiza una operación pero no devuelve
ningún valor.
Task<TResult>, para un método asincrónico que devuelve un valor.
void , para un controlador de eventos.
Cualquier tipo que tenga un método GetAwaiter accesible. El objeto devuelto por
el método GetAwaiter debe implementar la interfaz
System.Runtime.CompilerServices.ICriticalNotifyCompletion.
IAsyncEnumerable<T>, para un método asincrónico que devuelve una secuencia
asincrónica.
Para obtener más información sobre los métodos asincrónicos, vea Programación
asincrónica con async y await (C#).
También existen varios tipos que son específicos de las cargas de trabajo de Windows:
C#
await WaitAndApologizeAsync();
Console.WriteLine($"Today is {DateTime.Now:D}");
await Task.Delay(2000);
// Example output:
//
C#
Task waitAndApologizeTask = WaitAndApologizeAsync();
string output =
$"Today is {DateTime.Now:D}\n" +
await waitAndApologizeTask;
Console.WriteLine(output);
C#
string message =
$"Today is {DateTime.Today:D}\n" +
$"{await GetLeisureHoursAsync()}";
Console.WriteLine(message);
int leisureHours =
? 16 : 5;
return leisureHours;
// Example output:
) Importante
C#
string message =
$"Today is {DateTime.Today:D}\n" +
$"{await getLeisureHoursTask}";
Console.WriteLine(message);
C#
Clicked?.Invoke(this, EventArgs.Empty);
button.Clicked += OnButtonClicked1;
button.Clicked += OnButtonClicked2Async;
button.Clicked += OnButtonClicked3;
button.Click();
await secondHandlerFinished;
Task.Delay(100).Wait();
Task.Delay(100).Wait();
s_tcs.SetResult(true);
Task.Delay(100).Wait();
// Example output:
//
// Handler 1 is starting...
// Handler 1 is done.
// Handler 2 is starting...
// Handler 3 is starting...
// Handler 3 is done.
// Handler 2 is done.
Esta característica es el complemento de las expresiones con await, que describe los
requisitos del operando de await . Los tipos de valor devueltos asincrónicos
generalizados permiten al compilador generar métodos async que devuelven tipos
diferentes. Los tipos de valor devueltos asincrónicos generalizados permitían mejoras de
rendimiento en las bibliotecas de .NET. Como Task y Task<TResult> son tipos de
referencia, la asignación de memoria en las rutas críticas para el rendimiento,
especialmente cuando las asignaciones se producen en ajustados bucles, puede afectar
negativamente al rendimiento. La compatibilidad para los tipos de valor devuelto
generalizados significa que puede devolver un tipo de valor ligero en lugar de un tipo
de referencia para evitar asignaciones de memoria adicionales.
C#
class Program
Console.WriteLine("Shaking dice...");
await Task.Delay(500);
return diceRoll;
// Example output:
// Shaking dice...
// You rolled 8
C#
string data =
En el ejemplo anterior, las líneas de una cadena se leen de forma asincrónica. Una vez
que se ha leído cada línea, el código enumera cada palabra de la cadena. Los autores de
la llamada enumerarían cada palabra mediante la instrucción await foreach . El método
espera cuando necesita leer de forma asincrónica la línea siguiente de la cadena de
origen.
Vea también
FromResult
Procesamiento de tareas asincrónicas a medida que se completan
Programación asincrónica con async y await (C#)
async
await
Cancelar una lista de tareas
Artículo • 20/02/2023 • Tiempo de lectura: 5 minutos
Requisitos previos
Este tutorial requiere lo siguiente:
C#
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
Adición de campos
Dentro de la definición de la clase Program , agregue estos tres campos:
C#
MaxResponseContentBufferSize = 1_000_000
};
"https://learn.microsoft.com",
"https://learn.microsoft.com/aspnet/core",
"https://learn.microsoft.com/azure",
"https://learn.microsoft.com/azure/devops",
"https://learn.microsoft.com/dotnet",
"https://learn.microsoft.com/dynamics365",
"https://learn.microsoft.com/education",
"https://learn.microsoft.com/enterprise-mobility-security",
"https://learn.microsoft.com/gaming",
"https://learn.microsoft.com/graph",
"https://learn.microsoft.com/microsoft-365",
"https://learn.microsoft.com/office",
"https://learn.microsoft.com/powershell",
"https://learn.microsoft.com/sql",
"https://learn.microsoft.com/surface",
"https://learn.microsoft.com/system-center",
"https://learn.microsoft.com/visualstudio",
"https://learn.microsoft.com/windows",
"https://learn.microsoft.com/xamarin"
};
C#
Console.WriteLine("Application started.");
s_cts.Cancel();
});
if (finishedTask == cancelTask)
try
await sumPageSizesTask;
catch (TaskCanceledException)
Console.WriteLine("Application ending.");
El método Main actualizado ahora se considera un método Async main, el cual permite
un punto de entrada asincrónico en el archivo ejecutable. Escribe algunos mensajes
informativos en la consola y, luego, declara una instancia de Task denominada
cancelTask , la cual leerá las pulsaciones de teclas de la consola. Si se presiona la tecla
C#
int total = 0;
total += contentLength;
stopwatch.Stop();
El método comienza creando una instancia e iniciando una clase Stopwatch. Luego,
recorre en bucle cada dirección URL en s_urlList y llama a ProcessUrlAsync . Con cada
iteración, se pasa el token s_cts.Token al método ProcessUrlAsync y el código devuelve
una clase Task<TResult>, donde TResult es un entero:
C#
int total = 0;
total += contentLength;
C#
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");
return content.Length;
Application started.
https://learn.microsoft.com 37,357
https://learn.microsoft.com/aspnet/core 85,589
https://learn.microsoft.com/azure 398,939
https://learn.microsoft.com/azure/devops 73,663
https://learn.microsoft.com/dotnet 67,452
https://learn.microsoft.com/dynamics365 48,582
https://learn.microsoft.com/education 22,924
Application ending.
Ejemplo completo
El código siguiente es el texto completo del archivo Program.cs para el ejemplo.
C#
using System.Diagnostics;
class Program
MaxResponseContentBufferSize = 1_000_000
};
"https://docs.microsoft.com",
"https://docs.microsoft.com/aspnet/core",
"https://docs.microsoft.com/azure",
"https://docs.microsoft.com/azure/devops",
"https://docs.microsoft.com/dotnet",
"https://docs.microsoft.com/dynamics365",
"https://docs.microsoft.com/education",
"https://docs.microsoft.com/enterprise-mobility-security",
"https://docs.microsoft.com/gaming",
"https://docs.microsoft.com/graph",
"https://docs.microsoft.com/microsoft-365",
"https://docs.microsoft.com/office",
"https://docs.microsoft.com/powershell",
"https://docs.microsoft.com/sql",
"https://docs.microsoft.com/surface",
"https://docs.microsoft.com/system-center",
"https://docs.microsoft.com/visualstudio",
"https://docs.microsoft.com/windows",
"https://docs.microsoft.com/xamarin"
};
Console.WriteLine("Application started.");
s_cts.Cancel();
});
if (finishedTask == cancelTask)
try
await sumPageSizesTask;
catch (TaskCanceledException)
Console.WriteLine("Application ending.");
int total = 0;
total += contentLength;
stopwatch.Stop();
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");
return content.Length;
Vea también
CancellationToken
CancellationTokenSource
Programación asincrónica con async y await (C#)
Pasos siguientes
Cancelar tareas asincrónicas tras un período de tiempo (C#)
Cancelación de tareas asincrónicas tras
un período de tiempo
Artículo • 20/02/2023 • Tiempo de lectura: 2 minutos
Requisitos previos
Este tutorial requiere lo siguiente:
Que haya creado una aplicación en el tutorial Cancelación de una lista de tareas
(C#)
.NET 5 o un SDK posterior
Entorno de desarrollo integrado (IDE)
Se recomienda usar Visual Studio, Visual Studio Code o Visual Studio para
Mac .
C#
Console.WriteLine("Application started.");
try
s_cts.CancelAfter(3500);
await SumPageSizesAsync();
catch (OperationCanceledException)
finally
s_cts.Dispose();
Console.WriteLine("Application ending.");
Application started.
https://learn.microsoft.com 37,357
https://learn.microsoft.com/aspnet/core 85,589
https://learn.microsoft.com/azure 398,939
https://learn.microsoft.com/azure/devops 73,663
Application ending.
Ejemplo completo
El código siguiente es el texto completo del archivo Program.cs para el ejemplo.
C#
using System.Diagnostics;
class Program
MaxResponseContentBufferSize = 1_000_000
};
"https://docs.microsoft.com",
"https://docs.microsoft.com/aspnet/core",
"https://docs.microsoft.com/azure",
"https://docs.microsoft.com/azure/devops",
"https://docs.microsoft.com/dotnet",
"https://docs.microsoft.com/dynamics365",
"https://docs.microsoft.com/education",
"https://docs.microsoft.com/enterprise-mobility-security",
"https://docs.microsoft.com/gaming",
"https://docs.microsoft.com/graph",
"https://docs.microsoft.com/microsoft-365",
"https://docs.microsoft.com/office",
"https://docs.microsoft.com/powershell",
"https://docs.microsoft.com/sql",
"https://docs.microsoft.com/surface",
"https://docs.microsoft.com/system-center",
"https://docs.microsoft.com/visualstudio",
"https://docs.microsoft.com/windows",
"https://docs.microsoft.com/xamarin"
};
Console.WriteLine("Application started.");
try
s_cts.CancelAfter(3500);
await SumPageSizesAsync();
catch (OperationCanceledException)
finally
s_cts.Dispose();
Console.WriteLine("Application ending.");
int total = 0;
total += contentLength;
stopwatch.Stop();
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");
return content.Length;
Vea también
CancellationToken
CancellationTokenSource
Programación asincrónica con async y await (C#)
Cancelación de una lista de tareas (C#)
Iniciar varias tareas asincrónicas y
procesarlas a medida que se completan
(C#)
Artículo • 20/02/2023 • Tiempo de lectura: 9 minutos
Si usa Task.WhenAny, puede iniciar varias tareas a la vez y procesarlas una por una a
medida que se completen, en lugar de procesarlas en el orden en el que se han iniciado.
En el siguiente ejemplo se usa una consulta para crear una colección de tareas. Cada
tarea descarga el contenido de un sitio web especificado. En cada iteración de un bucle
while, una llamada awaited a WhenAny devuelve la tarea en la colección de tareas que
termine primero su descarga. Esa tarea se quita de la colección y se procesa. El bucle se
repite hasta que la colección no contiene más tareas.
Prerrequisitos
Puede seguir este tutorial mediante una de las opciones siguientes:
C#
using System.Diagnostics;
namespace ProcessTasksAsTheyFinish;
class Program
Console.WriteLine("Hello World!");
Adición de campos
Dentro de la definición de la clase Program , agregue los dos campos siguientes:
C#
MaxResponseContentBufferSize = 1_000_000
};
"https://learn.microsoft.com",
"https://learn.microsoft.com/aspnet/core",
"https://learn.microsoft.com/azure",
"https://learn.microsoft.com/azure/devops",
"https://learn.microsoft.com/dotnet",
"https://learn.microsoft.com/dynamics365",
"https://learn.microsoft.com/education",
"https://learn.microsoft.com/enterprise-mobility-security",
"https://learn.microsoft.com/gaming",
"https://learn.microsoft.com/graph",
"https://learn.microsoft.com/microsoft-365",
"https://learn.microsoft.com/office",
"https://learn.microsoft.com/powershell",
"https://learn.microsoft.com/sql",
"https://learn.microsoft.com/surface",
"https://learn.microsoft.com/system-center",
"https://learn.microsoft.com/visualstudio",
"https://learn.microsoft.com/windows",
"https://learn.microsoft.com/xamarin"
};
C#
El método Main actualizado ahora se considera un método Async main, el cual permite
un punto de entrada asincrónico en el archivo ejecutable. Se expresa como una llamada
a SumPageSizesAsync .
C#
IEnumerable<Task<int>> downloadTasksQuery =
int total = 0;
while (downloadTasks.Any())
downloadTasks.Remove(finishedTask);
stopwatch.Stop();
El bucle while quita una de las tareas de cada iteración. Una vez completadas todas las
tareas, el bucle finaliza. El método comienza creando una instancia e iniciando una clase
Stopwatch. Después, incluye una consulta que, cuando se ejecuta, crea una colección de
tareas. Cada llamada a ProcessUrlAsync en el siguiente código devuelve un objeto
Task<TResult>, donde TResult es un entero:
C#
IEnumerable<Task<int>> downloadTasksQuery =
Debido a la ejecución diferida con LINQ, se llama a Enumerable.ToList para iniciar cada
tarea.
C#
El bucle while realiza los pasos siguientes para cada tarea de la colección:
1. Espera una llamada a WhenAny para identificar la primera tarea de la colección que
ha finalizado su descarga.
C#
C#
downloadTasks.Remove(finishedTask);
C#
C#
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");
return content.Length;
Ejecute el programa varias veces para comprobar que las longitudes que se han
descargado no aparecen siempre en el mismo orden.
U Precaución
Ejemplo completo
El código siguiente es el texto completo del archivo Program.cs para el ejemplo.
C#
using System.Diagnostics;
MaxResponseContentBufferSize = 1_000_000
};
"https://docs.microsoft.com",
"https://docs.microsoft.com/aspnet/core",
"https://docs.microsoft.com/azure",
"https://docs.microsoft.com/azure/devops",
"https://docs.microsoft.com/dotnet",
"https://docs.microsoft.com/dynamics365",
"https://docs.microsoft.com/education",
"https://docs.microsoft.com/enterprise-mobility-security",
"https://docs.microsoft.com/gaming",
"https://docs.microsoft.com/graph",
"https://docs.microsoft.com/microsoft-365",
"https://docs.microsoft.com/office",
"https://docs.microsoft.com/powershell",
"https://docs.microsoft.com/sql",
"https://docs.microsoft.com/surface",
"https://docs.microsoft.com/system-center",
"https://docs.microsoft.com/visualstudio",
"https://docs.microsoft.com/windows",
"https://docs.microsoft.com/xamarin"
};
await SumPageSizesAsync();
IEnumerable<Task<int>> downloadTasksQuery =
int total = 0;
while (downloadTasks.Any())
downloadTasks.Remove(finishedTask);
stopwatch.Stop();
Console.WriteLine($"{url,-60} {content.Length,10:#,#}");
return content.Length;
// Example output:
// https://docs.microsoft.com 132,517
// https://docs.microsoft.com/powershell 57,375
// https://docs.microsoft.com/gaming 33,549
// https://docs.microsoft.com/aspnet/core 88,714
// https://docs.microsoft.com/surface 39,840
// https://docs.microsoft.com/enterprise-mobility-security 30,903
// https://docs.microsoft.com/microsoft-365 67,867
// https://docs.microsoft.com/windows 26,816
// https://docs.microsoft.com/xamarin 57,958
// https://docs.microsoft.com/dotnet 78,706
// https://docs.microsoft.com/graph 48,277
// https://docs.microsoft.com/dynamics365 49,042
// https://docs.microsoft.com/office 67,867
// https://docs.microsoft.com/system-center 42,887
// https://docs.microsoft.com/education 38,636
// https://docs.microsoft.com/azure 421,663
// https://docs.microsoft.com/visualstudio 30,925
// https://docs.microsoft.com/sql 54,608
// https://docs.microsoft.com/azure/devops 86,034
Vea también
WhenAny
Programación asincrónica con async y await (C#)
Acceso asincrónico a archivos (C#)
Artículo • 13/02/2023 • Tiempo de lectura: 5 minutos
Puede usar la característica async para acceder a archivos. Con la característica async, se
puede llamar a métodos asincrónicos sin usar devoluciones de llamada ni dividir el
código en varios métodos o expresiones lambda. Para convertir código sincrónico en
asincrónico, basta con llamar a un método asincrónico y no a un método sincrónico y
agregar algunas palabras clave al código.
Podrían considerarse los siguientes motivos para agregar asincronía a las llamadas de
acceso a archivos:
Escritura de texto
En los siguientes ejemplos se escribe texto en un archivo. En cada instrucción await, el
método finaliza inmediatamente. Cuando se complete la E/S de archivo, el método se
reanuda en la instrucción que sigue a la instrucción await. El modificador async se
encuentra en la definición de métodos que usan la instrucción await.
Ejemplo sencillo
C#
new FileStream(
filePath,
C#
await theTask;
Lectura de texto
En los ejemplos siguientes se lee texto de un archivo.
Ejemplo sencillo
C#
Console.WriteLine(text);
try
if (File.Exists(filePath) != false)
Console.WriteLine(text);
else
Console.WriteLine(ex.Message);
new FileStream(
filePath,
int numRead;
sb.Append(text);
return sb.ToString();
writeTaskList.Add(File.WriteAllTextAsync(filePath, text));
await Task.WhenAll(writeTaskList);
Tras completar las tareas, el ejemplo cierra todas las instancias de FileStream de un
bloque finally . Si en lugar de ello, cada FileStream se ha creado en una instrucción
using , la FileStream se podría desechar antes de completarse la tarea.
C#
try
var sourceStream =
new FileStream(
filePath,
sourceStreams.Add(sourceStream);
writeTaskList.Add(writeTask);
await Task.WhenAll(writeTaskList);
finally
sourceStream.Close();
}
Vea también
Programación asincrónica con async y await (C#)
Tipos de valor devueltos asincrónicos (C#)
Atributos (C#)
Artículo • 09/02/2023 • Tiempo de lectura: 5 minutos
Uso de atributos
Los atributos se pueden colocar en la mayoría de las declaraciones, aunque un
determinado atributo podría restringir los tipos de declaraciones en que es válido. En
C#, para especificar un atributo se coloca su nombre entre corchetes ([]) por encima de
la declaración de la entidad a la que se aplica.
C#
[Serializable]
C#
[System.Runtime.InteropServices.DllImport("user32.dll")]
extern static void SampleMethod();
C#
using System.Runtime.InteropServices;
C#
Algunos atributos se pueden especificar más de una vez para una entidad determinada.
Un ejemplo de este tipo de atributos multiuso es ConditionalAttribute:
C#
[Conditional("DEBUG"), Conditional("TEST1")]
void TraceMethod()
// ...
7 Nota
Por convención, todos los nombres de atributos terminan con la palabra "Attribute"
para distinguirlos de otros elementos de las bibliotecas de .NET. Sin embargo, no
es necesario especificar el sufijo de atributo cuando utiliza atributos en el código.
Por ejemplo, [DllImport] es equivalente a [DllImportAttribute] , pero
DllImportAttribute es el nombre real del atributo en la biblioteca de clases .NET.
Parámetros de atributo
Muchos atributos tienen parámetros, que pueden ser posicionales, sin nombre o con
nombre. Los parámetros posicionales deben especificarse en un orden determinado y
no se pueden omitir. Los parámetros con nombre son opcionales y pueden especificarse
en cualquier orden. Los parámetros posicionales se especifican en primer lugar. Por
ejemplo, estos tres atributos son equivalentes:
C#
[DllImport("user32.dll")]
El primer parámetro, el nombre del archivo DLL, es posicional y siempre va primero; los
demás tienen un nombre. En este caso, ambos parámetros con nombre tienen el estado
false de forma predeterminada, por lo que se pueden omitir. Los parámetros
posicionales corresponden a los parámetros del constructor de atributos. Los
parámetros con nombre u opcionales corresponden a propiedades o campos del
atributo. Consulte la documentación del atributo individual para obtener información
sobre los valores de parámetro predeterminados.
Para obtener más información sobre los tipos de parámetro admitidos, vea la sección
Atributos de la Especificación del lenguaje C#
Destinos de atributo
El destino de un atributo es la entidad a la que se aplica dicho atributo. Por ejemplo,
puede aplicar un atributo a una clase, un método determinado o un ensamblado
completo. De forma predeterminada, el atributo se aplica al elemento que sigue. Pero
puede identificar explícitamente, por ejemplo, si se aplica un atributo a un método, a su
parámetro o a su valor devuelto.
C#
[target : attribute-list]
event evento
property Propiedad.
Debe especificar el valor de destino field para aplicar un atributo al campo de respaldo
creado para una propiedad implementada automáticamente.
C#
using System;
using System.Reflection;
[module: CLSCompliant(true)]
C#
[ValidatedContract]
// applies to method
[method: ValidatedContract]
// applies to parameter
[return: ValidatedContract]
7 Nota
Marcar métodos con el atributo WebMethod en los servicios web para indicar que el
método debe ser invocable a través del protocolo SOAP. Para obtener más
información, vea WebMethodAttribute.
Describir cómo serializar parámetros de método al interoperar con código nativo.
Para obtener más información, vea MarshalAsAttribute.
Describir las propiedades COM para clases, métodos e interfaces.
Llamar al código no administrado mediante la clase DllImportAttribute.
Describir los ensamblados en cuanto a título, versión, descripción o marca.
Describir qué miembros de una clase serializar para la persistencia.
Describir cómo realizar asignaciones entre los miembros de clase y los nodos XML
para la serialización XML.
Describir los requisitos de seguridad para los métodos.
Especificar las características utilizadas para reforzar la seguridad.
Controlar optimizaciones mediante el compilador Just-In-Time (JIT) para que el
código siga siendo fácil de depurar.
Obtener información sobre el llamador de un método.
Secciones relacionadas
Para obtener más información, consulte:
Vea también
Guía de programación de C#
Reflexión (C#)
Atributos
Uso de atributos en C#
Crear atributos personalizados (C#)
Artículo • 04/01/2023 • Tiempo de lectura: 2 minutos
Para crear sus propios atributos personalizados, defina una clase de atributo derivada
directa o indirectamente de Attribute, que agiliza y facilita la identificación de las
definiciones de atributos en los metadatos. Imagínese que desea etiquetar tipos con el
nombre del programador que los escribió. Puede definir una clase de atributos Author
personalizada:
C#
[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Struct)
this.name = name;
version = 1.0;
de clase y de struct .
C#
class SampleClass
C#
[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Struct,
En el ejemplo de código siguiente se aplican varios atributos del mismo tipo a una clase.
C#
class SampleClass
Vea también
System.Reflection
Guía de programación de C#
Escribir atributos personalizados
Reflexión (C#)
Atributos (C#)
Acceder a atributos mediante reflexión (C#)
AttributeUsage (C#)
Acceder a atributos mediante reflexión
(C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
C#
class SampleClass
C#
anonymousAuthorObject.version = 1.1;
Ejemplo
Este es un ejemplo completo. Se define un atributo personalizado, se aplica a varias
entidades y se recupera mediante reflexión.
C#
// Multiuse attribute.
[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Struct,
string name;
this.name = name;
// Default value.
version = 1.0;
return name;
[Author("P. Ackerman")]
// ...
// ...
// ...
class TestAuthorAttribute
PrintAuthorInfo(typeof(FirstClass));
PrintAuthorInfo(typeof(SecondClass));
PrintAuthorInfo(typeof(ThirdClass));
// Using reflection.
// Displaying output.
if (attr is Author)
Author a = (Author)attr;
/* Output:
*/
Vea también
System.Reflection
Attribute
Guía de programación de C#
Recuperar información almacenada en atributos
Reflexión (C#)
Atributos (C#)
Crear atributos personalizados (C#)
Procedimiento para crear una unión de
C o C++ mediante atributos (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Ejemplos
En este segmento de código, todos los campos de TestUnion empiezan en la misma
ubicación en la memoria.
C#
[System.Runtime.InteropServices.StructLayout(LayoutKind.Explicit)]
struct TestUnion
[System.Runtime.InteropServices.FieldOffset(0)]
public int i;
[System.Runtime.InteropServices.FieldOffset(0)]
public double d;
[System.Runtime.InteropServices.FieldOffset(0)]
public char c;
[System.Runtime.InteropServices.FieldOffset(0)]
public byte b;
C#
[System.Runtime.InteropServices.StructLayout(LayoutKind.Explicit)]
struct TestExplicit
[System.Runtime.InteropServices.FieldOffset(0)]
[System.Runtime.InteropServices.FieldOffset(0)]
[System.Runtime.InteropServices.FieldOffset(0)]
[System.Runtime.InteropServices.FieldOffset(8)]
public double d;
[System.Runtime.InteropServices.FieldOffset(12)]
public char c;
[System.Runtime.InteropServices.FieldOffset(14)]
public byte b;
Los dos campos enteros, i1 e i2 , tiene las mismas ubicaciones en la memoria que lg .
Este tipo de control sobre el diseño del struct es útil cuando se usa la invocación de
plataforma.
Vea también
System.Reflection
Attribute
Guía de programación de C#
Atributos
Reflexión (C#)
Atributos (C#)
Crear atributos personalizados (C#)
Acceder a atributos mediante reflexión (C#)
Colecciones (C#)
Artículo • 10/02/2023 • Tiempo de lectura: 14 minutos
Para muchas aplicaciones, puede que desee crear y administrar grupos de objetos
relacionados. Existen dos formas de agrupar objetos: mediante la creación de matrices
de objetos y con la creación de colecciones de objetos.
Las matrices son muy útiles para crear y trabajar con un número fijo de objetos
fuertemente tipados. Para obtener información sobre las matrices, vea Matrices.
Las colecciones proporcionan una manera más flexible de trabajar con grupos de
objetos. A diferencia de las matrices, el grupo de objetos con el que trabaja puede
aumentar y reducirse de manera dinámica a medida que cambian las necesidades de la
aplicación. Para algunas colecciones, puede asignar una clave a cualquier objeto que
incluya en la colección para, de este modo, recuperar rápidamente el objeto con la
clave.
Una colección es una clase, por lo que debe declarar una instancia de la clase para
poder agregar elementos a dicha colección.
Si la colección contiene elementos de un solo tipo de datos, puede usar una de las
clases del espacio de nombres System.Collections.Generic. Una colección genérica
cumple la seguridad de tipos para que ningún otro tipo de datos se pueda agregar a
ella. Cuando recupera un elemento de una colección genérica, no tiene que determinar
su tipo de datos ni convertirlo.
7 Nota
Para los ejemplos de este tema, incluya las directivas using para los espacios de
nombres System.Collections.Generic y System.Linq .
En este tema
Tipos de colecciones
Clases System.Collections.Generic
Clases System.Collections.Concurrent
Clases System.Collections
Implementación de una colección de pares de clave/valor
Iteradores
C#
salmons.Add("chinook");
salmons.Add("coho");
salmons.Add("pink");
salmons.Add("sockeye");
C#
// collection initializer.
Puede usar una instrucción for en lugar de una instrucción foreach para recorrer en
iteración una colección. Esto se consigue con el acceso a los elementos de la colección
mediante la posición de índice. El índice de los elementos comienza en 0 y termina en el
número de elementos menos 1.
El ejemplo siguiente recorre en iteración los elementos de una colección mediante for
en lugar de foreach .
C#
// collection initializer.
C#
// collection initializer.
// the object.
salmons.Remove("coho");
El ejemplo siguiente quita elementos de una lista genérica. En lugar de una instrucción
foreach , se usa una instrucción for que procesa una iteración en orden descendente.
Esto es porque el método RemoveAt hace que los elementos después de un elemento
quitado tengan un valor de índice inferior.
C#
if (numbers[index] % 2 == 1)
numbers.RemoveAt(index);
numbers.ForEach(
// Output: 0 2 4 6 8
C#
};
// Output:
// Tadpole 400
// Pinwheel 25
// Milky Way 0
// Andromeda 3
Tipos de colecciones
.NET proporciona muchas colecciones comunes. Cada tipo de colección está diseñado
para un fin específico.
Clases System.Collections.Generic
Clases System.Collections.Concurrent
Clases System.Collections
Clases System.Collections.Generic
Puede crear una colección genérica mediante una de las clases del espacio de nombres
System.Collections.Generic. Una colección genérica es útil cuando todos los elementos
de la colección tienen el mismo tipo. Una colección genérica exige el establecimiento de
fuertes tipos al permitir agregar solo los tipos de datos deseados.
En la tabla siguiente se enumeran algunas de las clases usadas con frecuencia del
espacio de nombres System.Collections.Generic:
Clase Descripción
List<T> Representa una lista de objetos a los que puede tener acceso el
índice. Proporciona métodos para buscar, ordenar y modificar listas.
Clases System.Collections.Concurrent
En .NET Framework 4 y versiones posteriores, las colecciones del espacio de nombres
System.Collections.Concurrent proporcionan operaciones eficaces y seguras para
subprocesos con el fin de acceder a los elementos de la colección desde varios
subprocesos.
Clases System.Collections
Las clases del espacio de nombres System.Collections no almacenan los elementos
como objetos de tipo específico, sino como objetos del tipo Object .
Siempre que sea posible, debe usar las colecciones genéricas del espacio de nombres
System.Collections.Generic o del espacio de nombres System.Collections.Concurrent en
lugar de los tipos heredados del espacio de nombres System.Collections .
Clase Descripción
ArrayList Representa una matriz cuyo tamaño aumenta dinámicamente cuando es necesario.
Hashtable Representa una colección de pares de clave y valor que se organizan por código hash
de la clave.
Queue Representa una colección de objetos de primeras entradas, primeras salidas (FIFO).
Stack Representa una colección de objetos de últimas entradas, primeras salidas (LIFO).
El espacio de nombres System.Collections.Specialized proporciona clases de colección
especializadas y fuertemente tipadas como, por ejemplo, colecciones de solo cadena y
diccionarios híbridos y de lista vinculada.
C#
return elements;
theElement.Symbol = symbol;
theElement.Name = name;
theElement.AtomicNumber = atomicNumber;
C#
{"K",
{"Ca",
{"Sc",
{"Ti",
};
C#
if (elements.ContainsKey(symbol) == false)
else
C#
else
El ejemplo siguiente ejecuta una consulta LINQ en una List genérica. La consulta LINQ
devuelve otra colección que contiene los resultados.
C#
// LINQ Query.
orderby theElement.Name
select theElement;
// Output:
// Calcium 20
// Potassium 19
// Scandium 21
};
CompareTo.
Cada llamada al método CompareTo realiza una comparación única que se usa para la
ordenación. El código escrito por el usuario en el método CompareTo devuelve un valor
para cada comparación del objeto actual con otro objeto. El valor devuelto es menor
que cero si el objeto actual es menor que el otro objeto, mayor que cero si el objeto
actual es mayor que el otro objeto y cero si son iguales. Esto permite definir en el
código los criterios de mayor que, menor que e igual.
C#
};
// in descending order.
cars.Sort();
Console.Write(thisCar.Name);
Console.WriteLine();
// Output:
// blue 50 car4
// blue 30 car5
// blue 20 car1
// green 50 car7
// green 10 car3
// red 60 car6
// red 50 car2
// descending order.
int compare;
if (compare == 0)
compare = this.Speed.CompareTo(other.Speed);
compare = -compare;
return compare;
Aunque puede definir una colección personalizada, es mejor usar las colecciones
incluidas en .NET. Estas colecciones se describen en la sección Tipos de colecciones de
este artículo.
método GetEnumerator.
C#
Console.WriteLine();
// Collection class.
Color[] _colors =
};
//return _colors.GetEnumerator();
// Custom enumerator.
_colors = colors;
object System.Collections.IEnumerator.Current
get
return _colors[_position];
bool System.Collections.IEnumerator.MoveNext()
_position++;
void System.Collections.IEnumerator.Reset()
_position = -1;
// Element class.
Iterators
Los iteradores se usan para efectuar una iteración personalizada en una colección. Un
iterador puede ser un método o un descriptor de acceso get . Un iterador usa una
instrucción yield return para devolver cada elemento de la colección a la vez.
Llame a un iterador mediante una instrucción foreach. Cada iteración del bucle foreach
llama al iterador. Cuando se alcanza una instrucción yield return en el iterador, se
devuelve una expresión y se conserva la ubicación actual en el código. La ejecución se
reinicia desde esa ubicación la próxima vez que se llama al iterador.
El siguiente ejemplo usa el método del iterador. El método del iterador tiene una
instrucción yield return que se encuentra dentro de un bucle for . En el método
ListEvenNumbers , cada iteración del cuerpo de la instrucción foreach crea una llamada
al método iterador, que continúa con la siguiente instrucción yield return .
C#
Console.WriteLine();
// Output: 6 8 10 12 14 16 18
}
if (number % 2 == 0)
Vea también
Inicializadores de objeto y colección
Conceptos de programación (C#)
Option Strict (instrucción)
LINQ to Objects (C#)
Parallel LINQ (PLINQ)
Colecciones y estructuras de datos
Seleccionar una clase de colección
Comparaciones y ordenaciones en colecciones
Cuándo utilizar colecciones genéricas
Instrucciones de iteración
Covarianza y contravarianza (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
C#
// Assignment compatibility.
// Covariance.
// Contravariance.
C#
// array[0] = 10;
C#
C#
IEnumerable<T> (T es covariante)
IEnumerator<T> (T es covariante)
IQueryable<T> (T es covariante)
IComparer<T> (T es contravariante)
IEqualityComparer<T> (T es contravariante)
IComparable<T> (T es contravariante)
IReadOnlyList<T> (T es covariante)
IReadOnlyCollection<T> (T es covariante)
La covarianza permite que un método tenga un tipo de valor devuelto más derivado
que los que se definen en los parámetros de tipo genérico de la interfaz. Para ilustrar la
característica de la covarianza, considere estas interfaces genéricas: IEnumerable<Object>
y IEnumerable<String> . La interfaz IEnumerable<String> no hereda la interfaz
IEnumerable<Object> . En cambio, el tipo String hereda el tipo Object , y en algunos
casos puede que quiera asignar objetos de estas interfaces entre sí. Esto se muestra en
el ejemplo de código siguiente.
C#
C#
class BaseClass { }
// Comparer class.
return baseInstance.GetHashCode();
return x == y;
class Program
// IEqualityComparer<DerivedClass>.
Para obtener más ejemplos, vea Usar la varianza en interfaces para las colecciones
genéricas (C#).
La varianza para interfaces genéricas solo es compatible con tipos de referencia. Los
tipos de valor no admiten la varianza. Por ejemplo, IEnumerable<int> no puede
convertirse implícitamente en IEnumerable<object> , porque los enteros se representan
mediante un tipo de valor.
C#
También es importante recordar que las clases que implementan las interfaces variantes
siguen siendo invariables. Por ejemplo, aunque List<T> implementa la interfaz
covariante IEnumerable<T>, no puede convertir List<String> en List<Object>
implícitamente. Esto se muestra en el siguiente código de ejemplo.
C#
Vea también
Usar la varianza en interfaces para las colecciones genéricas (C#)
Crear interfaces genéricas variantes (C#)
Interfaces genéricas
Varianza en delegados (C#)
Crear interfaces genéricas variantes (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 5 minutos
7 Nota
) Importante
Los parámetros ref , in y out de C# no pueden ser variantes. Los tipos de valor
tampoco admiten la varianza.
Puede declarar un parámetro de tipo genérico covariante mediante la palabra clave out .
El tipo covariante debe cumplir las siguientes condiciones:
El tipo se usa únicamente como tipo de valor devuelto de los métodos de interfaz,
y no como tipo de los argumentos de método. Esto se muestra en el siguiente
ejemplo, en el que el tipo R se declara como covariante.
C#
R GetSomething();
Hay una excepción para esta regla. Si tiene un delegado genérico contravariante
como parámetro de método, puede usar el tipo como parámetro de tipo genérico
para el delegado. Esto se muestra en el siguiente ejemplo con el tipo R . Para
obtener más información, vea Varianza en delegados (C#) y Usar la varianza para
los delegados genéricos Func y Action (C#).
C#
El tipo no se usa como restricción genérica para los métodos de interfaz. Esto se
muestra en el siguiente código.
C#
// in generic constraints.
C#
// A GetSomething();
C#
R GetSomething();
R GetSetSomethings(A sampleArg);
C#
R GetSomething();
public R GetSomething()
// Some code.
return default(R);
Las clases que implementan interfaces variantes son invariables. Por ejemplo, considere
el fragmento de código siguiente:
C#
C#
Puede crear una interfaz que extienda la interfaz donde el parámetro de tipo genérico T
es covariante y la interfaz donde es contravariante si, en la interfaz que va a extender, el
parámetro de tipo genérico T es invariable. Esto se muestra en el siguiente código de
ejemplo.
C#
C#
Evitar la ambigüedad
Al implementar interfaces genéricas variantes, la varianza a veces puede implicar
ambigüedad. Debe evitarse esta ambigüedad.
C#
class Animal { }
IEnumerator<Cat> IEnumerable<Cat>.GetEnumerator()
Console.WriteLine("Cat");
// Some code.
return null;
IEnumerator IEnumerable.GetEnumerator()
// Some code.
return null;
IEnumerator<Dog> IEnumerable<Dog>.GetEnumerator()
Console.WriteLine("Dog");
// Some code.
return null;
class Program
pets.GetEnumerator();
Vea también
Varianza en interfaces genéricas (C#)
Usar varianza para los delegados genéricos Func y Action (C#)
Usar la varianza en interfaces para las
colecciones genéricas (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Una interfaz covariante permite que sus métodos devuelvan tipos más derivados que los
especificados en la interfaz. Una interfaz contravariante permite que sus métodos
acepten parámetros de tipos menos derivados que los especificados en la interfaz.
Para ver una lista de interfaces variantes de .NET, vea Varianza en interfaces genéricas
(C#).
C#
class Program
person.FirstName, person.LastName);
PrintFullName(employees);
C#
Object.ReferenceEquals(y, null))
return false;
? 0 : person.FirstName.GetHashCode();
class Program
};
IEnumerable<Employee> noduplicates =
employees.Distinct<Employee>(new PersonComparer());
Vea también
Varianza en interfaces genéricas (C#)
Varianza en delegados (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 6 minutos
En .NET Framework 3.5 se presentó por primera vez la compatibilidad con la varianza
para hacer coincidir firmas de método con tipos de delegados en todos los delegados
en C#. Esto significa que puede asignar a los delegados no solo métodos con firmas
coincidentes, sino métodos que devuelven tipos más derivados (covarianza) o que
aceptan parámetros con tipos menos derivados (contravarianza) que el especificado por
el tipo de delegado. Esto incluye delegados genéricos y no genéricos.
Por ejemplo, consideremos el siguiente código, que tiene dos clases y dos delegados:
genéricos y no genéricos.
C#
C#
// Matching signature.
// No conversion is necessary.
Para obtener más ejemplos, vea Usar varianza en delegados (C#) y Usar la varianza para
los delegados genéricos Func y Action (C#).
C#
Si usa solo la compatibilidad con la varianza para hacer coincidir firmas de método con
tipos de delegados y no usa las palabras clave in y out , es posible que en algunas
ocasiones pueda crear instancias de delegados con métodos o expresiones lambda
idénticos, pero no pueda asignar un delegado a otro.
C#
Action<T1,T2>
Delegado Predicate<T>.
Delegado Comparison<T>.
Delegado Converter<TInput,TOutput>.
Para obtener más información y ejemplos, vea Using Variance for Func and Action
Generic Delegates (C#) (Usar varianza para delegados genéricos Func y Action (C#)).
C#
C#
) Importante
C#
C#
dvariant("test");
C#
// actStr += actObj;
// Delegate.Combine(actStr, actObj);
C#
// The type T is covariant.
int i = 0;
Vea también
Genéricos
Usar varianza para los delegados genéricos Func y Action (C#)
Procedimiento para combinar delegados (delegados de multidifusión)
Usar varianza en delegados (C#)
Artículo • 09/02/2023 • Tiempo de lectura: 2 minutos
Ejemplo 1: Covarianza
Descripción
En este ejemplo se muestra cómo se pueden usar delegados con métodos que tienen
tipos de valor devuelto derivados del tipo de valor devuelto en la firma del delegado. El
tipo de datos devuelto por DogsHandler es de tipo Dogs , que se deriva del tipo Mammals
definido en el delegado.
Código
C#
class Mammals {}
class Program
return null;
return null;
Ejemplo 2: Contravarianza
Descripción
En este ejemplo se muestra cómo se pueden usar delegados con métodos que tienen
parámetros que son tipos base del tipo de parámetro de la firma del delegado. Con la
contravarianza, puede usar un controlador de eventos en lugar de controladores
independientes. En el ejemplo siguiente se usan dos delegados:
C#
C#
Código
C#
label1.Text = System.DateTime.Now.ToString();
public Form1()
InitializeComponent();
this.button1.KeyDown += this.MultiHandler;
this.button1.MouseClick += this.MultiHandler;
Consulte también
Varianza en delegados (C#)
Usar varianza para los delegados genéricos Func y Action (C#)
Usar la varianza para los delegados
genéricos Func y Action (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
C#
class Program
findPerson = findEmployee;
C#
class Program
// to a contact list.
addEmployeeToContacts = addPersonToContacts;
Vea también
Covarianza y contravarianza (C#)
Genéricos
Expression Trees
Artículo • 09/03/2023 • Tiempo de lectura: 4 minutos
Expression trees represent code in a tree-like data structure, where each node is an
expression, for example, a method call or a binary operation such as x < y .
If you have used LINQ, you have experience with a rich library where the Func types are
part of the API set. (If you aren't familiar with LINQ, you probably want to read the LINQ
tutorial and the article about lambda expressions before this one.) Expression Trees
provide richer interaction with the arguments that are functions.
You write function arguments, typically using Lambda Expressions, when you create
LINQ queries. In a typical LINQ query, those function arguments are transformed into a
delegate the compiler creates.
You've likely already written code that uses Expression trees. Entity Framework's LINQ
APIs accept Expression trees as the arguments for the LINQ Query Expression Pattern.
That enables Entity Framework to translate the query you wrote in C# into SQL that
executes in the database engine. Another example is Moq , which is a popular
mocking framework for .NET.
When you want to have a richer interaction, you need to use Expression Trees. Expression
Trees represent code as a structure that you examine, modify, or execute. These tools
give you the power to manipulate code during run time. You write code that examines
running algorithms, or injects new capabilities. In more advanced scenarios, you modify
running algorithms and even translate C# expressions into another form for execution in
another environment.
You compile and run code represented by expression trees. Building and running
expression trees enables dynamic modification of executable code, the execution of
LINQ queries in various databases, and the creation of dynamic queries. For more
information about expression trees in LINQ, see How to use expression trees to build
dynamic queries.
Expression trees are also used in the dynamic language runtime (DLR) to provide
interoperability between dynamic languages and .NET and to enable compiler writers to
emit expression trees instead of Microsoft intermediate language (MSIL). For more
information about the DLR, see Dynamic Language Runtime Overview.
You can have the C# or Visual Basic compiler create an expression tree for you based on
an anonymous lambda expression, or you can create expression trees manually by using
the System.Linq.Expressions namespace.
When a lambda expression is assigned to a variable of type Expression<TDelegate>, the
compiler emits code to build an expression tree that represents the lambda expression.
The C# compiler generates expression trees only from expression lambdas (or single-
line lambdas). It can't parse statement lambdas (or multi-line lambdas). For more
information about lambda expressions in C#, see Lambda Expressions.
The following code examples demonstrate how to have the C# compiler create an
expression tree that represents the lambda expression num => num < 5 .
C#
You create expression trees in your code. You build the tree by creating each node and
attaching the nodes into a tree structure. You learn how to create expressions in the
article on building expression trees.
Expression trees are immutable. If you want to modify an expression tree, you must
construct a new expression tree by copying the existing one and replacing nodes in it.
You use an expression tree visitor to traverse the existing expression tree. For more
information, see the article on translating expression trees.
Once you build an expression tree, you execute the code represented by the expression
tree.
Limitations
There are some newer C# language elements that don't translate well into expression
trees. Expression trees can't contain await expressions, or async lambda expressions.
Many of the features added in C# 6 and later don't appear exactly as written in
expression trees. Instead, newer features are exposed in expression trees in the
equivalent, earlier syntax, where possible. Other constructs aren't available. It means that
code that interprets expression trees works the same when new language features are
introduced. However, the expression trees Even with these limitations, expression trees
do enable you to create dynamic algorithms that rely on interpreting and modifying
code that is represented as a data structure. It enables rich libraries such as Entity
Framework to accomplish what they do.
Expression trees won't support new expression node types. It would be a breaking
change for all libraries interpreting expression trees to introduce new node types. The
following list includes most C# language elements that can't be used:
Conditional methods that have been removed
base access
Method group expressions, including address-of (&) a method group, and
anonymous method expressions
References to local functions
Statements, including assignment ( = ) and statement bodied expressions
Partial methods with only a defining declaration
Unsafe pointer operations
dynamic operations
Coalescing operators with null or default literal left side, null coalescing
assignment, and the null propagating operator (?.)
Multi-dimensional array initializers, indexed properties, and dictionary initializers
throw expressions
Accessing static virtual or abstract interface members
Lambda expressions that have attributes
Interpolated strings
UTF-8 string conversions or UTF-8 string literals
Method invocations using variable arguments, named arguments or optional
arguments
Expressions using System.Index or System.Range, index "from end" (^) operator or
range expressions (..)
async lambda expressions or await expressions, including await foreach and await
using
Tuple literals, tuple conversions, tuple == or !=, or with expressions
Discards (_), deconstructing assignment, pattern matching is operator or the
pattern matching switch expression
COM call with ref omitted on the arguments
ref, in or out parameters, ref return values, out arguments, or any values of ref
struct type
Ejecución de árboles de expresión
Artículo • 13/03/2023 • Tiempo de lectura: 7 minutos
7 Nota
Si un árbol de expresión no representa una expresión lambda, puede crear una nueva
expresión lambda que tenga el árbol de expresión original como su cuerpo llamando al
método Lambda<TDelegate>(Expression, IEnumerable<ParameterExpression>). Luego
puede ejecutar la expresión lambda tal y como se ha descrito anteriormente en esta
sección.
En la mayoría de los casos, existe una asignación simple entre una expresión y su
delegado correspondiente. Por ejemplo, un árbol de expresión que se representa por
Expression<Func<int>> se convertiría a un delegado del tipo Func<int> . Para una
expresión lambda con cualquier tipo de valor devuelto y lista de argumentos, existe un
tipo de delegado que es el tipo de destino para el código ejecutable representado por
esa expresión lambda.
) Importante
y versiones posteriores.
C#
Console.WriteLine(answer);
C#
Console.WriteLine(result(4));
// Prints True.
Console.WriteLine(expr.Compile()(4));
C#
BinaryExpression be = Expression.Power(Expression.Constant(2d),
Expression.Constant(3d));
Expression<Func<double>> le = Expression.Lambda<Func<double>>(be);
Console.WriteLine(result);
// 8
Ejecución y duraciones
El código se ejecuta mediante la invocación del delegado que se crea al llamar a
LambdaExpression.Compile() . El código anterior, add.Compile() , devuelve un delegado.
U Precaución
Advertencias
Compilar una expresión lambda en un delegado e invocar ese delegado es una de las
operaciones más simples que se pueden realizar con un árbol de expresión. Pero incluso
con esta sencilla operación, hay advertencias que debe conocer.
Las expresiones lambda crean clausuras sobre las variables locales a las que se hace
referencia en la expresión. Debe garantizar que las variables que formarían parte del
delegado se pueden usar en la ubicación desde la que se llama a Compile , y cuando se
ejecuta el delegado resultante. El compilador garantiza que las variables estén en el
ámbito. Pero si la expresión tiene acceso a una variable que implementa IDisposable , es
posible que el código deseche el objeto mientras se sigue manteniendo en el árbol de
expresión.
Por ejemplo, este código funciona bien porque int no implementa IDisposable :
C#
private static Func<int, int> CreateBoundFunc()
return rVal;
El delegado capturó una referencia a la variable local constant . Esa variable es accesible
en cualquier momento posterior, cuando se ejecuta la función devuelta por
CreateBoundFunc .
C#
get
if (!isDisposed)
return 5;
isDisposed = true;
C#
return rVal;
El delegado devuelto por este método se clausuró sobre el objeto constant , que se
eliminó. (Se eliminó porque se declaró en una instrucción using ).
Ahora, al ejecutar el delegado devuelto desde este método, se produce una excepción
ObjectDisposedException en el punto de ejecución.
Parece extraño tener un error en tiempo de ejecución que representa una construcción
de tiempo de compilación, pero es el mundo al que entra cuando trabaja con árboles de
expresión.
Hay numerosas permutaciones de este problema, por lo que resulta difícil ofrecer
instrucciones generales para evitarlo. Tenga cuidado al obtener acceso a las variables
locales al definir expresiones y al obtener acceso al estado en el objeto actual
(representado por this ) al crear un árbol de expresión devuelto por una API pública.
Resumen
Los árboles de expresión que representan expresiones lambda se pueden compilar para
crear un delegado que se puede ejecutar. Los árboles de expresión proporcionan un
mecanismo para ejecutar el código representado por un árbol de expresión.
En este artículo, obtendrá información sobre cómo visitar cada nodo en un árbol de
expresión, mientras se crea una copia modificada de ese árbol de expresión. Trasladará
los árboles de expresión para comprender los algoritmos para poder trasladarlos a otro
entorno. Cambiará el algoritmo que se ha creado. Podría agregar el registro, interceptar
las llamadas de método y realizar un seguimiento de ellas, o con otros fines.
Aquí, una vez que se encuentre un nodo constante, se crea un nuevo nodo de
multiplicación cuyos elementos secundarios son la constante original y la constante 10 :
C#
if (original.NodeType == ExpressionType.Constant)
return Expression.Add(
ReplaceNodes(binaryExpression.Left),
ReplaceNodes(binaryExpression.Right));
return original;
Cree un nuevo árbol reemplazando el nodo original por el sustituto. Puede comprobar
los cambios mediante la compilación y ejecución del árbol reemplazado.
C#
Console.WriteLine(answer);
La creación de un árbol nuevo es una combinación de visitar los nodos del árbol
existente y crear nodos nuevos e insertarlos en el árbol. En el ejemplo anterior se
muestra la importancia de la inmutabilidad de los árboles de expresión. Observe que el
nuevo árbol creado anteriormente contiene una mezcla de los nodos recién creados y
los nodos del árbol existente. Los nodos se pueden usar en ambos árboles porque los
nodos del árbol existente no se pueden modificar. La reutilización de nodos da lugar a
importantes eficiencias de memoria. Los mismos nodos se pueden usar en un árbol o en
varios árboles de expresión. Dado que los nodos no se pueden modificar, se puede
volver a usar el mismo nodo siempre que sea necesario.
C#
// Aggregate, return constants, or the sum of the left and right operand.
exp.NodeType == ExpressionType.Constant ?
(int)((ConstantExpression)exp).Value :
aggregate(((BinaryExpression)exp).Left) +
aggregate(((BinaryExpression)exp).Right);
Console.WriteLine(theSum);
Aquí hay gran cantidad de código, pero los conceptos son accesibles. Este código visita
los elementos secundarios en una primera búsqueda de profundidad. Cuando encuentra
un nodo constante, el visitante devuelve el valor de la constante. Tras la visita a los dos
elementos secundarios por parte del visitante, dichos elementos han obtenido la suma
calculada para ese subárbol. El nodo de adición ahora puede calcular la suma. Una vez
que se visiten todos los nodos en el árbol de expresión, se habrá calculado la suma. Se
puede hacer el seguimiento de la ejecución ejecutando el ejemplo en el depurador y
realizando el seguimiento de la ejecución.
C#
if (exp.NodeType == ExpressionType.Constant)
return value;
else
return 0;
return sum;
Resultados
10
Found Constant: 1
Left is: 1
Found Constant: 2
Right is: 2
Computed sum: 3
Left is: 3
Found Constant: 3
Left is: 3
Found Constant: 4
Right is: 4
Computed sum: 7
Right is: 7
Computed sum: 10
10
Realice el seguimiento del resultado y siga el código anterior. Debería poder averiguar
cómo el código visita cada nodo y calcula la suma mientras recorre el árbol y busca la
suma.
Ahora, veremos una ejecución diferente, con la expresión proporcionada por sum1 :
C#
Resultados
Found Addition Expression
Found Constant: 1
Left is: 1
Found Constant: 2
Left is: 2
Found Constant: 3
Left is: 3
Found Constant: 4
Right is: 4
Computed sum: 7
Right is: 7
Computed sum: 9
Right is: 9
Computed sum: 10
10
Aunque la respuesta final es la misma, el recorrido del árbol es diferente. Los nodos se
recorren en un orden diferente, porque el árbol se construyó con diferentes operaciones
que se producen en primer lugar.
C#
return Visit(expression);
if (b.NodeType == ExpressionType.AndAlso)
return base.VisitBinary(b);
C#
Console.WriteLine(expr);
Console.WriteLine(modifiedExpr);
*/
El código crea una expresión que contiene una operación AND condicional. Luego crea
una instancia de la clase AndAlsoModifier y pasa la expresión al método Modify de esta
clase. Se generan los árboles de expresiones tanto originales como modificados para
mostrar el cambio. Compile y ejecute la aplicación.
Más información
En este ejemplo se muestra un pequeño subconjunto del código que se compilaría para
recorrer e interpretar los algoritmos representados por un árbol de expresión. Para
información sobre la compilación de una biblioteca de propósito general que traduce
árboles de expresión a otro lenguaje, lea esta serie de Matt Warren. Describe en detalle
cómo traducir cualquier código que es posible encontrar en un árbol de expresión.
ParameterExpression
los nombres de variable ParameterExpression se muestran con un símbolo $ al
principio.
C#
/*
$num
*/
/*
$var1
*/
ConstantExpression
Para los objetos ConstantExpression que representan valores enteros, cadenas y null ,
se muestra el valor de la constante.
Para los tipos numéricos que tienen sufijos estándar como literales de C#, el sufijo se
agrega al valor. En la tabla siguiente se muestran los sufijos asociados a varios tipos
numéricos.
System.UInt32 uint U
Tipo Palabra clave Sufijo
System.Int64 long L
System.UInt64 ulong UL
System.Double double D
System.Single float F
System.Decimal decimal M
C#
/*
10
*/
/*
10D
*/
BlockExpression
Si el tipo de un objeto BlockExpression difiere del tipo de la última expresión del bloque,
el tipo se muestra entre corchetes angulares ( < y > ). De otro modo, el tipo del objeto
BlockExpression no se muestra.
C#
/*
.Block() {
"test"
*/
/*
.Block<System.Object>() {
"test"
*/
LambdaExpression
Los objetos LambdaExpression se muestran junto con sus tipos delegados.
C#
/*
.Lambda #Lambda1<System.Func'1[System.Int32]>() {
*/
/*
.Lambda #SampleLambda<System.Func'1[System.Int32]>() {
*/
LabelExpression
Si especifica un valor predeterminado para el objeto LabelExpression, este valor se
muestra antes del objeto LabelTarget.
C#
Expression.Goto(target, Expression.Constant(0)),
Expression.Label(target, Expression.Constant(-1))
);
/*
.Block() {
.Goto SampleLabel { 0 };
.Label
-1
.LabelTarget SampleLabel:
}
*/
Expression.Goto(target),
Expression.Label(target)
);
/*
.Block() {
.Goto #Label1 { };
.Label
.LabelTarget #Label1:
*/
Operadores activados
Los operadores activados se muestran con el símbolo # delante del operador. Por
ejemplo, el operador de adición activado se muestra como #+ .
C#
/*
1 #+ 2
*/
/*
#(System.Int32)10D
*/
Puesto que DebugView es una cadena, puede usar el visualizador de texto integrado para
verla en varias líneas si selecciona Visualizador de texto en el icono de lupa situado
junto a la etiqueta DebugView .
Vea también
Depurar en Visual Studio
Create Custom Visualizers (Crear visualizadores personalizados)
Sintaxis DebugView
Consultas basadas en el estado del
entorno de ejecución (C#)
Artículo • 13/03/2023 • Tiempo de lectura: 9 minutos
7 Nota
C#
};
// We're using an in-memory array as the data source, but the IQueryable
could have come
Cada vez que ejecute este código, se ejecutará la misma consulta exacta. Esto no suele
ser muy útil, ya que es posible que quiera que el código ejecute otras consultas en
función de las condiciones en tiempo de ejecución. En este artículo se describe cómo
puede ejecutar otra consulta en función del estado del entorno de ejecución.
Los árboles de expresión son inmutables; si quieres otro árbol de expresión (y, por
tanto, otra consulta), tendrás que convertir el existente en uno nuevo y, por tanto, en
una nueva instancia de IQueryable.
C#
var length = 1;
.Distinct();
Console.WriteLine(string.Join(",", qry));
// prints: C, A, S, W, G, H, M, N, B, T, L, F
length = 2;
Console.WriteLine(string.Join(",", qry));
// prints: Co, Al, So, Ci, Wi, Gr, Ad, Hu, Wo, Ma, No, Bl, Tr, Th, Lu, Fo
El árbol de expresión interno (y, por tanto, la consulta) no se han modificado; la consulta
solo devuelve otros valores porque se ha cambiado el valor de length .
C#
if (sortByLength)
C#
};
También es posible que quiera crear las distintas subexpresiones mediante una
biblioteca de terceros, como PredicateBuilder de LinqKit :
C#
// using LinqKit;
if (!string.IsNullOrEmpty(startsWith))
if (!string.IsNullOrEmpty(endsWith))
if (expr == original)
C#
Los pasos básicos para crear una instancia de Expression<TDelegate> son los
siguientes:
Defina objetos ParameterExpression para cada uno de los parámetros (si existen)
de la expresión lambda, mediante el método generador Parameter.
C#
C#
x,
Constant("a")
);
C#
Expression<Func<string, bool>> expr = Lambda<Func<string, bool>>(body,
x);
Escenario
Imagine que tiene varios tipos de entidad:
C#
En cualquiera de estos tipos de entidad, quiere filtrar y devolver solo las entidades que
contengan un texto concreto dentro de uno de sus campos string . Para Person , le
interesa buscar las propiedades FirstName y LastName :
C#
.AsQueryable()
C#
.AsQueryable()
Aunque podría escribir una función personalizada para IQueryable<Person> y otra para
IQueryable<Car> , la siguiente función agrega este filtrado a cualquier consulta existente,
Ejemplo
C#
// using static System.Linq.Expressions.Expression;
PropertyInfo[] stringProperties =
elementType.GetProperties()
.ToArray();
.Select(prp =>
Call( // .Contains(...)
Property( // .PropertyName
prm, // x
prp
),
containsMethod,
Constant(term) // "term"
);
);
return source.Where(lambda);
C#
new List<Person>().AsQueryable(),
"abcd"
new List<Car>().AsQueryable(),
"abcd"
También puede duplicar la función del método de LINQ y encapsular todo el árbol en un
elemento MethodCallExpression que represente una llamada al método de LINQ:
C#
typeof(Queryable),
"Where",
new[] { elementType},
source.Expression,
Lambda(body, prm!)
);
return source.Provider.CreateQuery(filteredTree);
C#
// using System.Linq.Dynamic.Core
var stringProperties =
elementType.GetProperties()
.ToArray();
" || ",
);
Vea también
Árboles de expresión (C#)
Ejecución de árboles de expresión
Especificación de filtros con predicado de forma dinámica en tiempo de ejecución
Iteradores (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 8 minutos
Para consumir un método iterador desde código de cliente, use una instrucción foreach
o una consulta de LINQ.
En el ejemplo siguiente, la primera iteración del bucle foreach hace que continúe la
ejecución del método de iterador SomeNumbers hasta que se alcance la primera
instrucción yield return . Esta iteración devuelve un valor de 3, y la ubicación actual del
método de iterador se conserva. En la siguiente iteración del bucle, la ejecución del
método iterador continúa desde donde se dejó, deteniéndose de nuevo al alcanzar una
instrucción yield return . Esta iteración devuelve un valor de 5, y la ubicación actual del
método de iterador se vuelve a conservar. El bucle se completa al alcanzar el final del
método iterador.
C#
// Output: 3 5 8
Console.ReadKey();
yield return 3;
yield return 5;
yield return 8;
7 Nota
Iterador simple
El ejemplo siguiente tiene una única instrucción yield return que está dentro de un
bucle for. En Main , cada iteración del cuerpo de la instrucción foreach crea una llamada
a la función de iterador, que continúa a la instrucción yield return siguiente.
C#
// Output: 6 8 10 12 14 16 18
Console.ReadKey();
if (number % 2 == 0)
C#
Console.ReadKey();
En el ejemplo siguiente se crea una clase Zoo que contiene una colección de animales.
C#
theZoo.AddMammal("Whale");
theZoo.AddMammal("Rhinoceros");
theZoo.AddBird("Penguin");
theZoo.AddBird("Warbler");
Console.WriteLine();
Console.WriteLine();
Console.WriteLine();
Console.ReadKey();
// Private members.
// Public methods.
// Public members.
// Private methods.
if (theAnimal.Type == type)
// Private class.
El ejemplo usa iteradores con nombre para admitir distintas formas de recorrer en
iteración la misma colección de datos. Estos iteradores con nombre son las propiedades
TopToBottom y BottomToTop , y el método TopN .
C#
theStack.Push(number);
Console.WriteLine();
// Output: 9 8 7 6 5 4 3 2 1 0
Console.WriteLine();
// Output: 9 8 7 6 5 4 3 2 1 0
Console.WriteLine();
// Output: 0 1 2 3 4 5 6 7 8 9
Console.WriteLine();
// Output: 9 8 7 6 5 4 3
Console.ReadKey();
values[top] = t;
top++;
public T Pop()
top--;
return values[top];
IEnumerator IEnumerable.GetEnumerator()
return GetEnumerator();
get
Debe existir una conversión implícita desde el tipo de expresión en la instrucción yield
return al argumento de tipo para el valor IEnumerable<T> devuelto por el iterador.
En C#, yield no es una palabra reservada y solo tiene un significado especial cuando se
usa antes de una palabra clave return o break .
Implementación técnica
Aunque un iterador se escribe como un método, el compilador lo traduce a una clase
anidada que es, en realidad, una máquina de estados. Esta clase realiza el seguimiento
de la posición del iterador mientras el bucle foreach continúe en el código de cliente.
Para ver lo que hace el compilador, puede usar la herramienta Ildasm.exe para ver el
código de lenguaje intermedio de Microsoft que se genera para un método de iterador.
Cuando crea un iterador para una clase o struct, no necesita implementar la interfaz
IEnumerator completa. Cuando el compilador detecta el iterador, genera
automáticamente los métodos Current , MoveNext y Dispose de la interfaz IEnumerator
o IEnumerator<T>.
Uso de iteradores
Los iteradores permiten mantener la simplicidad de un bucle foreach cuando se
necesita usar código complejo para rellenar una secuencia de lista. Esto puede ser útil si
quiere hacer lo siguiente:
Modificar la secuencia de lista después de la primera iteración del bucle foreach .
Evitar que se cargue totalmente una lista grande antes de la primera iteración de
un bucle foreach . Un ejemplo es una búsqueda paginada para cargar un lote de
filas de tabla. Otro ejemplo es el método EnumerateFiles, que implementa
iteradores en .NET.
Vea también
System.Collections.Generic
IEnumerable<T>
foreach, in
Utilizar foreach con matrices
Genéricos
Language Integrated Query (LINQ) (C#)
Artículo • 10/02/2023 • Tiempo de lectura: 4 minutos
Para un desarrollador que escribe consultas, la parte más visible de "lenguaje integrado"
de LINQ es la expresión de consulta. Las expresiones de consulta se escriben con una
sintaxis de consulta declarativa. Con la sintaxis de consulta, puede realizar operaciones
de filtrado, ordenación y agrupamiento en orígenes de datos con el mínimo código.
Utilice los mismos patrones de expresión de consulta básica para consultar y
transformar datos de bases de datos SQL, conjuntos de datos de ADO .NET, secuencias
y documentos XML y colecciones. NET.
Puede escribir consultas LINQ en C# para bases de datos de SQL Server, documentos
XML, conjuntos de datos ADO.NET y cualquier colección de objetos que admita
IEnumerable o la interfaz genérica IEnumerable<T>. La compatibilidad con LINQ
también se proporciona por terceros para muchos servicios web y otras
implementaciones de base de datos.
C#
IEnumerable<int> scoreQuery =
select score;
// Output: 97 92 81
Pasos siguientes
Para obtener más información sobre LINQ, empiece a familiarizarse con algunos
conceptos básicos en Conceptos básicos de las expresiones de consultas y, después, lea
la documentación de la tecnología de LINQ en la que esté interesado:
Para comprender mejor los aspectos generales de LINQ, vea LINQ in C# (LINQ en C#).
Para empezar a trabajar con LINQ en C#, vea el tutorial Trabajar con LINQ.
Introducción a las consultas LINQ (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 6 minutos
Una consulta es una expresión que recupera datos de un origen de datos. Las consultas
se suelen expresar en un lenguaje de consultas especializado. Con el tiempo se han
desarrollado diferentes lenguajes para los distintos tipos de orígenes de datos, como
SQL para las bases de datos relacionales y XQuery para XML. Por lo tanto, los
programadores han tenido que aprender un lenguaje de consultas nuevo para cada tipo
de origen de datos o formato de datos que deben admitir. LINQ simplifica esta situación
al ofrecer un modelo coherente para trabajar con los datos de varios formatos y
orígenes. En una consulta LINQ siempre se trabaja con objetos. Se usan los mismos
patrones de codificación básicos para consultar y transformar datos de documentos
XML, bases de datos SQL, conjuntos de datos de ADO.NET, colecciones de .NET y
cualquier otro formato para el que haya disponible un proveedor de LINQ.
2. Crear la consulta.
3. Ejecutar la consulta.
En el siguiente ejemplo se muestra cómo se expresan las tres partes de una operación
de consulta en código fuente. En el ejemplo se usa una matriz de enteros como origen
de datos para su comodidad, aunque se aplican los mismos conceptos a otros orígenes
de datos. En el resto de este tema se hará referencia a este ejemplo.
C#
class IntroToLINQ
// 1. Data source.
// 2. Query creation.
// numQuery is an IEnumerable<int>
var numQuery =
where (num % 2) == 0
select num;
// 3. Query execution.
El origen de datos
En el ejemplo anterior, como el origen de datos es una matriz, admite implícitamente la
interfaz genérica IEnumerable<T>. Este hecho implica que se puede consultar con LINQ.
Se ejecuta una consulta en una instrucción foreach , y foreach requiere IEnumerable o
bien IEnumerable<T>. Los tipos compatibles con IEnumerable<T> o una interfaz
derivada, como la interfaz genérica IQueryable<T>, se denominan tipos consultables.
// using System.Xml.Linq;
Con LINQ to SQL, primero se crea una asignación relacional de objetos en tiempo de
diseño, ya sea manualmente o mediante las herramientas de LINQ to SQL en
Visual Studio. Se escriben las consultas en los objetos y, en tiempo de ejecución, LINQ to
SQL controla la comunicación con la base de datos. En el ejemplo siguiente, Customers
representa una tabla específica en una base de datos, y el tipo del resultado de la
consulta, IQueryable<T>, se deriva de IEnumerable<T>.
C#
select cust;
Para obtener más información sobre cómo crear tipos específicos de orígenes de datos,
consulte la documentación de los distintos proveedores de LINQ. Aun así, la regla básica
es muy sencilla: un origen de datos de LINQ es cualquier objeto que admita la interfaz
genérica IEnumerable<T> o una interfaz que la haya heredado.
7 Nota
Consulta
La consulta especifica la información que se debe recuperar de los orígenes de datos.
Opcionalmente, una consulta también especifica cómo se debe ordenar, agrupar y
conformar esa información antes de que se devuelva. Las consultas se almacenan en
una variable de consulta y se inicializan con una expresión de consulta. Para facilitar la
escritura de consultas, C# ha incorporado una nueva sintaxis de consulta.
La consulta del ejemplo anterior devuelve todos los números pares de la matriz de
enteros. La expresión de consulta contiene tres cláusulas: from , where y select (si está
familiarizado con SQL, habrá observado que el orden de las cláusulas se invierte
respecto al orden de SQL). La cláusula from especifica el origen de datos, la cláusula
where aplica el filtro y la cláusula select especifica el tipo de los elementos devueltos.
7 Nota
Las consultas también se pueden expresar empleando una sintaxis de método. Para
obtener más información, vea Query Syntax and Method Syntax in LINQ (Sintaxis
de consulta y sintaxis de método en LINQ).
Ejecución de la consulta
Ejecución aplazada
Como se ha indicado anteriormente, la variable de consulta solo almacena los
comandos de consulta. La ejecución real de la consulta se aplaza hasta que se procese
una iteración en la variable de consulta en una instrucción foreach . Este concepto se
conoce como ejecución aplazada y se muestra en el ejemplo siguiente:
C#
// Query execution.
C#
var evenNumQuery =
where (num % 2) == 0
select num;
C#
List<int> numQuery2 =
where (num % 2) == 0
select num).ToList();
// or like this:
var numQuery3 =
where (num % 2) == 0
select num).ToArray();
Vea también
Introducción a LINQ en C#
Tutorial: Escribir consultas en C#
Language-Integrated Query (LINQ)
foreach, in
Palabras clave para consultas (LINQ)
LINQ y tipos genéricos (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
1. Al crear una instancia de una clase de colección genérica como List<T>, reemplace
la "T" por el tipo de objetos que contendrá la lista. Por ejemplo, una lista de
cadenas se expresa como List<string> y una lista de objetos Customer se expresa
como List<Customer> . Las listas genéricas están fuertemente tipadas y ofrecen
muchas ventajas respecto a las colecciones que almacenan sus elementos como
Object. Si intenta agregar un Customer a una List<string> , se producirá un error
en tiempo de compilación. Usar colecciones genéricas es fácil porque no es
necesario efectuar ninguna conversión de tipos en tiempo de ejecución.
C#
IEnumerable<Customer> customerQuery =
select cust;
Para obtener más información, vea Relaciones entre tipos en operaciones de consulta
LINQ.
C#
var customerQuery2 =
select cust;
La palabra clave var es útil cuando el tipo de la variable es obvio o cuando no es tan
importante especificar explícitamente los tipos genéricos anidados, como los que
generan las consultas de grupo. Le recordamos que, si usa var , debe tener presente
que puede dificultar la lectura del código a otros usuarios. Para más información, vea
Variables locales con asignación implícita de tipos.
Vea también
Genéricos
Operaciones básicas de consulta LINQ
(C#)
Artículo • 15/02/2023 • Tiempo de lectura: 5 minutos
En este tema se ofrece una breve introducción a las expresiones de consulta LINQ y
algunas de las clases de operaciones típicas que se realizan en una consulta. En los
temas siguientes se ofrece información más detallada:
7 Nota
C#
//queryAllCustomers is an IEnumerable<Customer>
select cust;
variables de rango adicionales. Para obtener más información, vea let (Cláusula).
7 Nota
Filtrado
Probablemente la operación de consulta más común es aplicar un filtro en forma de
expresión booleana. El filtro hace que la consulta devuelva solo los elementos para los
que la expresión es verdadera. El resultado se genera mediante la cláusula where . El
filtro aplicado especifica qué elementos se deben excluir de la secuencia de origen. En el
ejemplo siguiente, solo se devuelven los customers cuya dirección se encuentra en
Londres.
C#
select cust;
Puede usar los operadores lógicos AND y OR de C#, con los que ya estará familiarizado,
para aplicar las expresiones de filtro que sean necesarias en la cláusula where . Por
ejemplo, para devolver solo los clientes con dirección en "London" AND cuyo nombre
sea "Devon", escribiría el código siguiente:
C#
Para devolver los clientes con dirección en Londres o París, escribiría el código siguiente:
C#
C#
var queryLondonCustomers3 =
select cust;
Agrupar
La cláusula group permite agrupar los resultados según la clave que se especifique. Por
ejemplo, podría especificar que los resultados se agrupen por City para que todos los
clientes de London o París estén en grupos individuales. En este caso, la clave es
cust.City .
C#
var queryCustomersByCity =
Console.WriteLine(customerGroup.Key);
Al finalizar una consulta con una cláusula group , los resultados adoptan la forma de una
lista de listas. Cada elemento de la lista es un objeto que tiene un miembro Key y una
lista de elementos agrupados bajo esa clave. Al procesar una iteración en una consulta
que genera una secuencia de grupos, debe usar un bucle foreach anidado. El bucle
exterior recorre en iteración cada grupo y el bucle interior recorre en iteración los
miembros de cada grupo.
Si debe hacer referencia a los resultados de una operación de grupo, puede usar la
palabra clave into para crear un identificador con el que se puedan realizar más
consultas. La consulta siguiente devuelve solo los grupos que contienen más de dos
clientes:
C#
var custQuery =
orderby custGroup.Key
select custGroup;
Combinación
Las operaciones de combinación crean asociaciones entre las secuencias que no se
modelan explícitamente en los orígenes de datos. Por ejemplo, puede realizar una
combinación para buscar todos los clientes y distribuidores que tengan la misma
ubicación. En LINQ, la cláusula join funciona siempre con colecciones de objetos, en
lugar de con tablas de base de datos directamente.
C#
var innerJoinQuery =
En LINQ no es necesario usar join tan a menudo como en SQL, porque las claves
externas en LINQ se representan en el modelo de objetos como propiedades que
contienen una colección de elementos. Por ejemplo, un objeto Customer contiene una
colección de objetos Order . En lugar de realizar una combinación, tiene acceso a los
pedidos usando la notación de punto:
C#
Selección (proyecciones)
La cláusula select genera resultados de consulta y especifica la "forma" o el tipo de
cada elemento devuelto. Por ejemplo, puede especificar si sus resultados estarán
compuestos de objetos Customer completos, un solo miembro, un subconjunto de
miembros o algún tipo de resultado completamente diferente basado en un cálculo o
en un objeto nuevo. Cuando la cláusula select genera algo distinto de una copia del
elemento de origen, la operación se denomina proyección. El uso de proyecciones para
transformar los datos es una función eficaz de las expresiones de consulta LINQ. Para
obtener más información, vea Transformaciones de datos con LINQ (C#) y select
(cláusula).
Consulte también
Expresiones de consulta LINQ
Tutorial: Creación de consultas en C#
Palabras clave para consultas (LINQ)
Tipos anónimos
Transformaciones de datos con LINQ
(C#)
Artículo • 15/02/2023 • Tiempo de lectura: 6 minutos
Combinar varias secuencias de entrada en una sola secuencia de salida que tiene
un tipo nuevo.
Crear secuencias de salida cuyos elementos estén formados por una o varias
propiedades de cada elemento de la secuencia de origen.
Crear secuencias de salida cuyos elementos estén formados por los resultados de
las operaciones realizadas en el origen de datos.
Estos son solo algunos ejemplos. Por supuesto, estas transformaciones pueden
combinarse de diversas formas en la misma consulta. Además, se puede usar la
secuencia de salida de una consulta como la secuencia de entrada para una nueva
consulta.
C#
class Student
class Teacher
C#
class DataTransformations
Last="Omelchenko",
ID=111,
Street="123 Main Street",
City="Seattle",
Last="O’Donnell",
ID=112,
Street="124 Main Street",
City="Redmond",
Last="Mortensen",
ID=113,
Street="125 Main Street",
City="Lake City",
};
};
select student.Last)
select teacher.Last);
Console.WriteLine(person);
Console.ReadKey();
/* Output:
Omelchenko
Beebe
*/
C#
var query = from cust in Customers
select cust.City;
2. Para crear elementos que contengan más de una propiedad del elemento de
origen, se puede usar un inicializador de objeto con un objeto con nombre o un
tipo anónimo. En el ejemplo siguiente se muestra el uso de un tipo anónimo para
encapsular dos propiedades de cada elemento Customer :
C#
C#
class XMLTransform
};
) // end "student"
); // end "Root"
Console.WriteLine(studentsToXML);
Console.ReadKey();
XML
<Root>
<student>
<First>Svetlana</First>
<Last>Omelchenko</Last>
<Scores>97,92,81,60</Scores>
</student>
<student>
<First>Claire</First>
<Last>O'Donnell</Last>
<Scores>75,84,91,39</Scores>
</student>
<student>
<First>Sven</First>
<Last>Mortensen</Last>
<Scores>88,94,65,91</Scores>
</student>
</Root>
Para obtener más información, vea Creating XML Trees (C#) (Creación de árboles XML
[C#]).
7 Nota
C#
class FormatQuery
// Data source.
double[] radii = { 1, 2, 3 };
IEnumerable<string> output =
/*
IEnumerable<string> output =
*/
Console.WriteLine(s);
}
Console.ReadKey();
/* Output:
*/
Vea también
Language Integrated Query (LINQ) (C#)
LINQ to SQL
LINQ to DataSet
LINQ to XML (C#)
Expresiones de consulta LINQ
select (cláusula)
Relaciones entre tipos en operaciones
de consulta LINQ (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
Para escribir las consultas eficazmente, es necesario comprender cómo los tipos de las
variables en una operación de consulta completa se relacionan entre sí. Si entiende
estas relaciones comprenderá más fácilmente los ejemplos de LINQ y los ejemplos de
código de la documentación. Además, entenderá lo que sucede en segundo plano
cuando los tipos de las variables se declaran implícitamente mediante var .
Para mostrar estas relaciones de tipos, en la mayoría de los ejemplos siguientes se usan
tipos explícitos para todas las variables. En el último ejemplo se muestra cómo se
aplican los mismos principios incluso al usar tipos implícitos mediante var.
Para obtener más información sobre var , vea Variables locales con asignación implícita
de tipos.
Sintaxis de consultas y sintaxis de
métodos en LINQ (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 5 minutos
C#
class QueryVMethodSyntax
//Query syntax:
IEnumerable<int> numQuery1 =
where num % 2 == 0
orderby num
select num;
//Method syntax:
Console.WriteLine(System.Environment.NewLine);
Console.WriteLine(System.Environment.NewLine);
Console.ReadKey();
/*
Output:
6 8 10 12
6 8 10 12
*/
El resultado de los dos ejemplos es idéntico. Como puede ver, el tipo de variable de
consulta es el mismo en ambos formularios: IEnumerable<T>.
Para empezar a usar LINK, lo único que realmente debe saber sobre los métodos de
extensión es cómo incluirlos en el ámbito de la aplicación mediante el uso correcto de
directivas using . Desde el punto de vista de la aplicación, un método de extensión y un
método de instancia normal son iguales.
Para obtener más información sobre los métodos de extensión, vea Métodos de
extensión. Para obtener más información sobre los operadores de consulta estándar, vea
Información general sobre operadores de consulta estándar (C#). Algunos proveedores
LINQ, como LINQ to SQL y LINQ to XML, implementan sus propios operadores de
consulta estándar y otros métodos de extensión adicionales para otros tipos además de
IEnumerable<T>.
Expresiones lambda
En el ejemplo anterior, observe que la expresión condicional ( num % 2 == 0 ) se pasa
como argumento insertado al método Where : Where(num => num % 2 == 0). Esta
expresión insertada se denomina expresión lambda. Se trata de una forma cómoda de
escribir código que, de lo contrario, tendría que escribirse de forma más compleja como
un método anónimo, un delegado genérico o un árbol de expresión. En C#, => es el
operador lambda, que se lee como "va a". La num situada a la izquierda del operador es
la variable de entrada que corresponde a num en la expresión de consulta. El compilador
puede deducir el tipo de num porque sabe que numbers es un tipo IEnumerable<T>
genérico. El cuerpo de la expresión lambda es exactamente igual que la expresión de la
sintaxis de consulta o de cualquier otra expresión o instrucción de C#; puede incluir
llamadas de método y otra lógica compleja. El "valor devuelto" es simplemente el
resultado de la expresión.
Para empezar a usar LINK, no es necesario emplear muchas expresiones lambda. Pero
determinadas consultas solo se pueden expresar en sintaxis de método y algunas
requieren expresiones lambda. Cuando esté más familiarizado con las expresiones
lambda, verá que se trata de una herramienta eficaz y flexible del cuadro de
herramientas de LINK. Para obtener más información, vea Expresiones lambda.
Aunque estas nuevas características se usan hasta cierto punto con consultas LINQ, no
se limitan a LINQ y se pueden usar en cualquier contexto en las que se consideren de
utilidad.
Expresiones de consulta
Las expresiones de consulta usan una sintaxis declarativa similar a SQL o XQuery para
consultar colecciones de IEnumerable. En tiempo de compilación, la sintaxis de consulta
se convierte en llamadas de método a la implementación de un proveedor de LINQ de
los métodos de extensión de operador de consulta estándar. Las aplicaciones controlan
los operadores de consulta estándar que están en el ámbito al especificar el espacio de
nombres adecuado con una directiva using . La siguiente expresión de consulta toma
una matriz de cadenas, las agrupa por el primer carácter de la cadena y ordena los
grupos.
C#
orderby stringGroup.Key
select stringGroup;
C#
var number = 5;
select str;
Las variables declaradas como var son tan fuertemente tipadas como las variables cuyo
tipo se especifica explícitamente. El uso de var hace posible crear tipos anónimos, pero
solo se puede usar para variables locales. También se pueden declarar matrices con
asignación implícita de tipos.
Para más información, vea Variables locales con asignación implícita de tipos.
C#
Continuando con nuestra clase Customer , suponga que hay un origen de datos
denominado IncomingOrders y que para cada pedido con un OrderSize grande, nos
gustaría crear un nuevo Customer basado en ese orden. Se pueden ejecutar una
consulta LINQ en este origen de datos y usar la inicialización de objetos para rellenar
una colección:
C#
El origen de datos puede tener más propiedades ocultas en el montón que la clase
Customer , como OrderSize , pero con la inicialización de objetos, los datos devueltos por
la consulta se moldean en el tipo de datos deseado; elegimos los datos que son
relevantes para nuestra clase. Como resultado, ahora tenemos un IEnumerable relleno
con el nuevo Customer que queríamos. Lo anterior también se puede escribir en la
sintaxis de método de LINQ:
C#
var newLargeOrderCustomers = IncomingOrders.Where(x => x.OrderSize >
5).Select(y => new Customer { Name = y.Name, Phone = y.Phone });
Tipos anónimos
Un tipo anónimo se construye por el compilador y el nombre del tipo solo está
disponible para el compilador. Los tipos anónimos son una manera cómoda de agrupar
un conjunto de propiedades temporalmente en un resultado de consulta sin tener que
definir un tipo con nombre independiente. Los tipos anónimos se inicializan con una
nueva expresión y un inicializador de objeto, como se muestra aquí:
C#
Métodos de extensión.
Un método de extensión es un método estático que se puede asociar con un tipo, por
lo que puede llamarse como si fuera un método de instancia en el tipo. Esta
característica permite, en efecto, "agregar" nuevos métodos a los tipos existentes sin
tener que modificarlos realmente. Los operadores de consulta estándar son un conjunto
de métodos de extensión que proporcionan funciones de consultas LINQ para cualquier
tipo que implemente IEnumerable<T>.
Expresiones lambda
Una expresión lambda es una función insertada que usa el operador => para separar los
parámetros de entrada del cuerpo de la función y que se puede convertir en tiempo de
compilación en un delegado o un árbol de expresión. En la programación de LINQ, se
encuentran expresiones lambda al realizar llamadas de método directas a los
operadores de consulta estándar.
Expresiones lambda
Vea también
Language Integrated Query (LINQ) (C#)
Tutorial: Escribir consultas en C# (LINQ)
Artículo • 10/02/2023 • Tiempo de lectura: 10 minutos
Este tutorial muestra las características del lenguaje C# que se usan para escribir
expresiones de consulta de LINQ.
Crear un proyecto de C#
7 Nota
Toda la estructura de datos se inicializará y creará una instancia sin llamadas explícitas a
ningún constructor ni acceso a miembro explícito. Para obtener más información sobre
estas nuevas características, vea Propiedades autoimplementadas (Propiedades
implementadas automáticamente) y Inicializadores de objeto y de colección.
C#
};
Crear la consulta
C#
IEnumerable<Student> studentQuery =
select student;
Ejecutar la consulta
1. Escriba ahora el bucle foreach que hará que la consulta se ejecute. Tenga en
cuenta los siguiente sobre el código:
A cada elemento de la secuencia devuelta se accede mediante la variable de
iteración del bucle foreach .
2. Tras agregar este código, compile y ejecute la aplicación para ver los resultados en
la ventana Consola.
C#
// Output:
// Omelchenko, Svetlana
// Garcia, Cesar
// Fakhouri, Fadi
// Feng, Hanying
// Garcia, Hugo
// Adams, Terry
// Zabokritski, Eugene
// Tucker, Michael
C#
Modificar la consulta
Para ordenar los resultados
1. Le resultará más fácil examinar los resultados si se muestran con algún tipo de
orden. Puede ordenar la secuencia devuelta por cualquier campo accesible de los
elementos de origen. Por ejemplo, la cláusula orderby siguiente ordena los
resultados alfabéticamente de la A a la Z por el apellido de cada alumno. Agregue
la cláusula orderby siguiente a la consulta, inmediatamente después de la
instrucción where y antes de la instrucción select :
C#
2. Cambie ahora la cláusula orderby para que ordene los resultados en orden inverso
según la puntuación en la primera prueba, de la puntuación más alta a la más baja.
C#
C#
C#
var studentQuery2 =
C#
Console.WriteLine(studentGroup.Key);
student.Last, student.First);
// Output:
// O
// Omelchenko, Svetlana
// O'Donnell, Claire
// M
// Mortensen, Sven
// G
// Garcia, Cesar
// Garcia, Debra
// Garcia, Hugo
// F
// Fakhouri, Fadi
// Feng, Hanying
// T
// Tucker, Lance
// Tucker, Michael
// A
// Adams, Terry
// Z
// Zabokritski, Eugene
C#
var studentQuery3 =
Console.WriteLine(groupOfStudents.Key);
student.Last, student.First);
// Output:
// O
// Omelchenko, Svetlana
// O'Donnell, Claire
// M
// Mortensen, Sven
// G
// Garcia, Cesar
// Garcia, Debra
// Garcia, Hugo
// F
// Fakhouri, Fadi
// Feng, Hanying
// T
// Tucker, Lance
// Tucker, Michael
// A
// Adams, Terry
// Z
// Zabokritski, Eugene
Para obtener más información sobre var, vea Variables locales con asignación
implícita de tipos.
siguiente:
C#
var studentQuery4 =
orderby studentGroup.Key
select studentGroup;
Console.WriteLine(groupOfStudents.Key);
student.Last, student.First);
// Output:
//A
// Adams, Terry
//F
// Fakhouri, Fadi
// Feng, Hanying
//G
// Garcia, Cesar
// Garcia, Debra
// Garcia, Hugo
//M
// Mortensen, Sven
//O
// Omelchenko, Svetlana
// O'Donnell, Claire
//T
// Tucker, Lance
// Tucker, Michael
//Z
// Zabokritski, Eugene
Cuando ejecute esta consulta, verá que los grupos aparecen ahora ordenados
alfabéticamente.
Para incluir un identificador mediante let
1. Puede usar la palabra clave let para incluir un identificador con cualquier
resultado de la expresión en la expresión de consulta. Este identificador puede
resultar cómodo, como en el ejemplo siguiente, o mejorar el rendimiento
almacenando los resultados de una expresión para que no tenga que calcularse
varias veces.
C#
// studentQuery5 is an IEnumerable<string>
// average score.
var studentQuery5 =
student.Scores[2] + student.Scores[3]
Console.WriteLine(s);
// Output:
// Omelchenko Svetlana
// O'Donnell Claire
// Mortensen Sven
// Garcia Cesar
// Fakhouri Fadi
// Feng Hanying
// Garcia Hugo
// Adams Terry
// Zabokritski Eugene
// Tucker Michael
var studentQuery6 =
student.Scores[2] + student.Scores[3]
select totalScore;
// Output:
1. Es muy frecuente que una consulta genere una secuencia cuyos elementos difieren
de los elementos de las secuencias de origen. Elimine la consulta y el bucle de
ejecución anteriores o conviértalos en comentario, y reemplácelos por el código
siguiente. Tenga en cuenta que la consulta devuelve una secuencia de cadenas (no
Students ) y este hecho se refleja en el bucle foreach .
C#
IEnumerable<string> studentQuery7 =
select student.First;
Console.WriteLine(s);
// Output:
// Cesar
// Debra
// Hugo
C#
var studentQuery8 =
student.Scores[2] + student.Scores[3]
// Output:
Pasos siguientes
Cuando se haya familiarizado con los aspectos básicos del uso de consultas en C#,
estará preparado para leer la documentación y ejemplos del tipo específico de
proveedor LINQ que le interese:
LINQ to SQL
LINQ to DataSet
Vea también
Language Integrated Query (LINQ) (C#)
Expresiones de consulta LINQ
Información general sobre operadores
de consulta estándar (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 4 minutos
Los operadores de consulta estándar son los métodos que constituyen el modelo LINQ.
La mayoría de estos métodos funciona en secuencias; donde una secuencia es un objeto
cuyo tipo implementa la interfaz IEnumerable<T> o la interfaz IQueryable<T>. Los
operadores de consulta estándar ofrecen funcionalidades de consulta, como las
funciones de filtrado, proyección, agregación y ordenación, entre otras.
Hay dos conjuntos de operadores de consulta estándar de LINQ: uno que actúa sobre
objetos de tipo IEnumerable<T> y otro sobre objetos de tipo IQueryable<T>. Los
métodos que forman cada conjunto son miembros estáticos de las clases Enumerable y
Queryable, respectivamente. Se definen como métodos de extensión del tipo en el que
actúan. Se puede llamar a los métodos de extensión mediante sintaxis de método
estático o sintaxis de método de instancia.
En el caso de los métodos que actúan en colecciones en memoria, es decir, los métodos
que extienden IEnumerable<T>, el objeto enumerable devuelto captura los argumentos
que se han pasado al método. Cuando se enumera ese objeto, se emplea la lógica del
operador de consulta y se devuelven los resultados de la consulta.
C#
string sentence = "the quick brown fox jumps over the lazy dog";
orderby gr.Key
Console.WriteLine(word);
//
// Words of length 3:
// THE
// FOX
// THE
// DOG
// Words of length 4:
// OVER
// LAZY
// Words of length 5:
// QUICK
// BROWN
// JUMPS
Secciones relacionadas
Los vínculos siguientes llevan a artículos que ofrecen información adicional sobre los
distintos operadores de consulta estándar según la funcionalidad.
Algunos de los operadores de consulta estándar que se usan con más frecuencia tienen
una sintaxis especial de palabras clave de lenguaje C# para que se puedan invocar como
parte de una expresión de consulta. Una expresión de consulta constituye una forma
diferente de expresar una consulta, más legible que su equivalente basado en métodos.
Las cláusulas de las expresiones de consulta se convierten en llamadas a los métodos de
consulta en tiempo de compilación.
GroupBy group … by
o bien
group … by … into …
Func<TOuter,IEnumerable<TInner>,
TResult>)
(Para obtener más
información, vea join
(Cláusula, Referencia de
C#)).
Func<TOuter,TInner,TResult>)
(Para obtener más
información, vea join
(Cláusula, Referencia de
C#)).
OrderBy<TSource,TKey>(IEnumerable<TSource>, orderby
Func<TSource,TKey>)
(Para obtener más
información, vea orderby
(Cláusula)).
Func<TSource,TKey>)
(Para obtener más
información, vea orderby
(Cláusula)).
Select select
ThenBy<TSource,TKey>(IOrderedEnumerable<TSource>, orderby …, …
Func<TSource,TKey>)
(Para obtener más
información, vea orderby
(Cláusula)).
Método Sintaxis de la expresión
de consulta de C#
Func<TSource,TKey>)
(Para obtener más
información, vea orderby
(Cláusula)).
Where where
Consulte también
Enumerable
Queryable
Información general sobre operadores de consulta estándar (C#)
Clasificación de operadores de consulta estándar por modo de ejecución (C#)
Clasificación de operadores de consulta
estándar por modo de ejecución (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
Modos de ejecución
Inmediato
La ejecución inmediata significa que se lee el origen de datos y que la operación se
realiza una vez. Todos los operadores de consulta estándar que devuelven un resultado
escalar se ejecutan de manera inmediata. Puede forzar que una consulta se ejecute
inmediatamente mediante los métodos Enumerable.ToList y Enumerable.ToArray. La
ejecución inmediata permite reutilizar los resultados de la consulta, no su declaración.
Los resultados se recuperan una vez y, después, se almacenan para usarlos en el futuro.
Aplazada
La ejecución aplazada significa que la operación no se realiza en el punto en el código
donde se declara la consulta. La operación se realiza solo cuando se enumera la variable
de consulta, por ejemplo, mediante una instrucción foreach . Esto significa que los
resultados de ejecutar la consulta dependen del contenido del origen de datos cuando
se ejecuta la consulta en lugar de cuando se define la consulta. Si la variable de consulta
se enumera varias veces, es posible que los resultados difieran cada vez. Casi todos los
operadores de consulta estándar cuyo tipo de valor devuelto es IEnumerable<T> o
IOrderedEnumerable<TElement> se ejecutan de una manera diferida. La ejecución
diferida aporta la facilidad de reutilizar las consultas, ya que la consulta captura los
datos actualizados del origen de datos cada vez que se iteran los resultados de la
consulta.
Los operadores de consulta que usan la ejecución aplazada pueden clasificarse además
como de streaming o de no streaming.
Streaming
Los operadores de streaming no deben leer todos los datos de origen antes de que
produzcan elementos. En el momento de la ejecución, un operador de streaming realiza
su operación en cada elemento de origen mientras se lee y proporciona el elemento si
es necesario. Un operador de streaming continúa leyendo los elementos de origen hasta
que se puede generar un elemento de resultado. Esto significa que es posible leer más
de un elemento de origen para generar un elemento de resultado.
No streaming
Los operadores de no streaming deben leer todos los datos de origen antes de poder
proporcionar un elemento de resultado. Las operaciones como la ordenación o la
agrupación pertenecen a esta categoría. En tiempo de ejecución, los operadores de
consulta de no streaming leen todos los datos de origen, los colocan en una estructura
de datos, realizan la operación y proporcionan los elementos resultantes.
Tabla de clasificación
En la tabla siguiente se clasifica cada método de operador de consulta estándar según
su método de ejecución.
7 Nota
Aggregate TSource X
All Boolean X
Operador de Tipo devuelto Ejecución Ejecución Ejecución
consulta estándar inmediata aplazada aplazada
de de no
streaming streaming
Any Boolean X
AsEnumerable IEnumerable<T> X
Cast IEnumerable<T> X
Concat IEnumerable<T> X
Contains Boolean X
Count Int32 X
DefaultIfEmpty IEnumerable<T> X
Distinct IEnumerable<T> X
ElementAt TSource X
ElementAtOrDefault TSource X
Empty IEnumerable<T> X
Except IEnumerable<T> X X
First TSource X
FirstOrDefault TSource X
GroupBy IEnumerable<T> X
GroupJoin IEnumerable<T> X X
Intersect IEnumerable<T> X X
Join IEnumerable<T> X X
Last TSource X
LastOrDefault TSource X
LongCount Int64 X
OfType IEnumerable<T> X
OrderBy IOrderedEnumerable<TElement> X
OrderByDescending IOrderedEnumerable<TElement> X
Range IEnumerable<T> X
Repeat IEnumerable<T> X
Reverse IEnumerable<T> X
Select IEnumerable<T> X
SelectMany IEnumerable<T> X
SequenceEqual Boolean X
Single TSource X
SingleOrDefault TSource X
Skip IEnumerable<T> X
SkipWhile IEnumerable<T> X
Take IEnumerable<T> X
TakeWhile IEnumerable<T> X
ThenBy IOrderedEnumerable<TElement> X
ThenByDescending IOrderedEnumerable<TElement> X
ToDictionary Dictionary<TKey,TValue> X
ToList IList<T> X
ToLookup ILookup<TKey,TElement> X
Union IEnumerable<T> X
Operador de Tipo devuelto Ejecución Ejecución Ejecución
consulta estándar inmediata aplazada aplazada
de de no
streaming streaming
Where IEnumerable<T> X
Consulte también
Enumerable
Información general sobre operadores de consulta estándar (C#)
Sintaxis de las expresiones de consulta para operadores de consulta estándar (C#)
LINQ to Objects (C#)
Ordenación de datos (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Una operación de ordenación ordena los elementos de una secuencia según uno o
varios atributos. El primer criterio de ordenación realiza una ordenación primaria de los
elementos. Al especificar un segundo criterio de ordenación, se pueden ordenar los
elementos dentro de cada grupo de ordenación primaria.
Métodos
Nombre del Descripción Sintaxis de la Más información
método expresión de
consulta de C#
orden ascendente.
Queryable.OrderBy
ordenación
secundaria en orden Queryable.ThenBy
ascendente.
ordenación descending
secundaria en orden Queryable.ThenByDescending
descendente.
Nombre del Descripción Sintaxis de la Más información
método expresión de
consulta de C#
los elementos de
una colección. Queryable.Reverse
En el siguiente ejemplo se muestra cómo usar la cláusula orderby en una consulta LINQ
para ordenar las cadenas de una matriz por la longitud de la cadena, en orden
ascendente.
C#
orderby word.Length
select word;
Console.WriteLine(str);
the
fox
quick
brown
jumps
*/
select word;
Console.WriteLine(str);
the
quick
jumps
fox
brown
*/
En el siguiente ejemplo se muestra cómo usar la cláusula orderby en una consulta LINQ
para realizar una ordenación principal y secundaria de las cadenas de una matriz. Las
cadenas se ordenan primero por su longitud y, después, por la letra inicial de la cadena,
en orden ascendente.
C#
select word;
Console.WriteLine(str);
fox
the
brown
jumps
quick
*/
C#
select word;
Console.WriteLine(str);
the
fox
quick
jumps
brown
*/
Consulte también
System.Linq
Información general sobre operadores de consulta estándar (C#)
orderby (cláusula)
Ordenar los resultados de una cláusula join
Procedimiento para ordenar o filtrar datos de texto por palabra o campo (LINQ)
(C#)
Operaciones set [C#]
Artículo • 15/02/2023 • Tiempo de lectura: 7 minutos
Los métodos del operador de consulta estándar que realizan operaciones set se indican
en la sección siguiente.
Métodos
Nombres Descripción Sintaxis de la Información adicional
de expresión de
método consulta de C#
Queryable.Distinct
Queryable.DistinctBy
colección. Queryable.ExceptBy
Queryable.IntersectBy
colecciones. Queryable.UnionBy
Ejemplos
Algunos de los ejemplos siguientes se basan en un tipo record que representa los
planetas de nuestro sistema solar.
C#
namespace SolarSystem;
record Planet(
string Name,
PlanetType Type,
int OrderFromSun)
OrderFromSun para crear una instancia de él. Hay varias instancias de planeta static
C#
namespace SolarSystem;
enum PlanetType
Rock,
Ice,
Gas,
Liquid
};
Distinct y DistinctBy
En la siguiente ilustración se muestra el comportamiento del método
Enumerable.Distinct en una secuencia de cadenas. La secuencia devuelta contiene los
elementos únicos de la secuencia de entrada.
C#
select planet;
Console.WriteLine(str);
* Mercury
* Venus
* Earth
* Mars
*/
C#
Planet[] planets =
Planet.Mercury,
Planet.Venus,
Planet.Earth,
Planet.Mars,
Planet.Jupiter,
Planet.Saturn,
Planet.Uranus,
Planet.Neptune,
Planet.Pluto
};
C#
Console.WriteLine(planet);
En el código de C# anterior:
Except y ExceptBy
En el ejemplo siguiente se muestra el comportamiento de Enumerable.Except. La
secuencia devuelta solo contiene los elementos de la primera secuencia de entrada que
no están en la segunda secuencia de entrada.
C#
select planet;
Console.WriteLine(str);
* Venus
*/
C#
Planet[] planets =
Planet.Mercury,
Planet.Venus,
Planet.Earth,
Planet.Jupiter
};
Planet[] morePlanets =
Planet.Mercury,
Planet.Earth,
Planet.Mars,
Planet.Jupiter
};
C#
// A shared "keySelector"
planets.ExceptBy(
morePlanets.Select(PlanetNameSelector), PlanetNameSelector))
Console.WriteLine(planet);
En el código de C# anterior:
keySelector se define como una función static local, que discrimina por nombre
de planeta.
La primera matriz de planetas se filtra por planetas que no se encuentran en la
segunda matriz del planeta, en función de su nombre.
La instancia planet resultante se escribe en la consola.
Intersect y IntersectBy
En el ejemplo siguiente se muestra el comportamiento de Enumerable.Intersect. La
secuencia devuelta contiene los elementos que son comunes a las dos secuencias de
entrada.
C#
select planet;
Console.WriteLine(str);
* Mercury
* Earth
* Jupiter
*/
C#
Planet[] firstFivePlanetsFromTheSun =
Planet.Mercury,
Planet.Venus,
Planet.Earth,
Planet.Mars,
Planet.Jupiter
};
Planet[] lastFivePlanetsFromTheSun =
Planet.Mars,
Planet.Jupiter,
Planet.Saturn,
Planet.Uranus,
Planet.Neptune
};
Hay dos matrices de planetas, una representa los cinco primeros planetas desde el sol y
la segunda representa los últimos cinco planetas desde sol. Dado que el tipo Planet es
un tipo posicional record , se puede utilizar su semántica de comparación de valores en
la forma del keySelector :
C#
firstFivePlanetsFromTheSun.IntersectBy(
Console.WriteLine(planet);
En el código de C# anterior:
Union y UnionBy
En el siguiente ejemplo se muestra una operación de unión en dos secuencias de
cadenas. La secuencia devuelta contiene los elementos únicos de las dos secuencias de
entrada.
C#
select planet;
Console.WriteLine(str);
* Mercury
* Venus
* Earth
* Jupiter
* Mars
*/
UnionBy es un enfoque alternativo a Union que adopta dos secuencias del mismo tipo y
keySelector . keySelector se usa como discriminador comparativo del tipo de origen.
Considere las siguientes matrices de planetas:
C#
Planet[] firstFivePlanetsFromTheSun =
Planet.Mercury,
Planet.Venus,
Planet.Earth,
Planet.Mars,
Planet.Jupiter
};
Planet[] lastFivePlanetsFromTheSun =
Planet.Mars,
Planet.Jupiter,
Planet.Saturn,
Planet.Uranus,
Planet.Neptune
};
Para la unión de estas dos colecciones en una sola secuencia, proporcione keySelector :
C#
firstFivePlanetsFromTheSun.UnionBy(
Console.WriteLine(planet);
En el código de C# anterior:
Consulte también
System.Linq
Información general sobre operadores de consulta estándar (C#)
Procedimiento para combinar y comparar colecciones de cadenas (LINQ) (C#)
Procedimiento para buscar la diferencia de conjuntos entre dos listas (LINQ) (C#)
Filtrado de datos (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Los métodos del operador de consulta estándar que realizan selecciones se indican en la
sección siguiente.
Métodos
Nombre Descripción Sintaxis de la Más información
del expresión de
método consulta de C#
función de predicado.
Queryable.Where
C#
string[] words = { "the", "quick", "brown", "fox", "jumps" };
where word.Length == 3
select word;
Console.WriteLine(str);
the
fox
*/
Consulte también
System.Linq
Información general sobre operadores de consulta estándar (C#)
where (cláusula)
Especificación de filtros con predicado de forma dinámica en tiempo de ejecución
Procedimiento para consultar los metadatos de un ensamblado con reflexión
(LINQ) (C#)
Procedimiento para buscar archivos con un nombre o atributo especificados (C#)
Procedimiento para ordenar o filtrar datos de texto por palabra o campo (LINQ)
(C#)
Operaciones cuantificadoras en LINQ
(C#)
Artículo • 10/02/2023 • Tiempo de lectura: 3 minutos
Métodos
Nombre Descripción Sintaxis de la Más información
del expresión de
método consulta de C#
Todas
En el ejemplo siguiente se usa All para comprobar que todas las cadenas tienen una
longitud específica.
C#
class Market
};
select market.Name;
Console.WriteLine($"{name} market");
//
// Kim's market
Cualquiera
En el ejemplo siguiente se usa Any para comprobar que las cadenas se inician con "o".
C#
class Market
};
// Determine which market have any fruit names start with 'o'
select market.Name;
Console.WriteLine($"{name} market");
//
// Kim's market
// Adam's market
Contiene
En el ejemplo siguiente se usa Contains para comprobar que una matriz tenga un
elemento específico.
C#
class Market
};
where market.Items.Contains("kiwi")
select market.Name;
Console.WriteLine($"{name} market");
//
// Emily's market
// Adam's market
Consulte también
System.Linq
Información general sobre operadores de consulta estándar (C#)
Especificación de filtros con predicado de forma dinámica en tiempo de ejecución
Procedimiento para buscar frases que contengan un conjunto especificado de
palabras (LINQ) (C#)
Operaciones de proyección (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 6 minutos
Los métodos del operador de consulta estándar que realizan proyecciones se indican en
la sección siguiente.
Métodos
Nombres Descripción Sintaxis de la Información adicional
de método expresión de
consulta de
C#
Select
En el ejemplo siguiente se usa la cláusula select para proyectar la primera letra de cada
cadena de una lista de cadenas.
C#
Console.WriteLine(s);
*/
SelectMany
En el ejemplo siguiente se usan varias cláusulas from para proyectar cada palabra de
todas las cadenas de una lista de cadenas.
C#
List<string> phrases = new() { "an apple a day", "the quick brown fox" };
select word;
Console.WriteLine(s);
an
apple
day
the
quick
brown
fox
*/
Zip
Hay varias sobrecargas para el operador Zip de proyección. Todos los métodos Zip
funcionan en secuencias de dos o más tipos posiblemente heterogéneos. Las dos
primeras sobrecargas devuelven tuplas, con el tipo posicional correspondiente de las
secuencias dadas.
1, 2, 3, 4, 5, 6, 7
};
};
C#
) Importante
La secuencia resultante de una operación zip nunca tiene más longitud que la
secuencia más corta. Las colecciones numbers y letters difieren en longitud, y la
secuencia resultante omite el último elemento de la colección numbers , ya que no
tiene nada con que comprimir.
La segunda sobrecarga acepta una secuencia third . Vamos a crear otra colección,
concretamente emoji :
C#
};
C#
Console.WriteLine(
Al igual que la sobrecarga anterior, el método Zip proyecta una tupla, pero esta vez con
tres elementos.
C#
Console.WriteLine(result);
// 1 = A (65)
// 2 = B (66)
// 3 = C (67)
// 4 = D (68)
// 5 = E (69)
// 6 = F (70)
origen. Luego, SelectMany concatena estas secuencias enumerables para crear una
secuencia de gran tamaño.
Las dos ilustraciones siguientes muestran la diferencia conceptual entre las acciones de
estos dos métodos. En cada caso, se supone que la función de selector (transformación)
selecciona la matriz de flores de cada valor de origen.
En esta ilustración se muestra cómo Select devuelve una colección que tiene el mismo
número de elementos que la colección de origen.
C#
class Bouquet
};
Console.WriteLine(item);
Console.WriteLine(item);
daisy
daffodil
larkspur
tulip
rose
orchid
gladiolis
lily
snapdragon
aster
protea
larkspur
lilac
iris
dahlia
sunflower
daisy
daffodil
larkspur
tulip
rose
orchid
gladiolis
lily
snapdragon
aster
protea
larkspur
lilac
iris
dahlia
*/
Consulte también
System.Linq
Información general sobre operadores de consulta estándar (C#)
select (cláusula)
Procedimiento para rellenar colecciones de objetos de varios orígenes (LINQ) (C#)
Procedimiento para dividir un archivo en varios mediante el uso de grupos (LINQ)
(C#)
Realizar particiones de datos (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Operadores
Nombres Descripción Sintaxis de la Información
de expresión de adicional
método consulta de C#
Ejemplo
El operador Chunk se usa para dividir los elementos de una secuencia en función de un
valor size determinado.
C#
int chunkNumber = 1;
Console.WriteLine($"Chunk {chunkNumber++}:");
Console.WriteLine($" {item}");
Console.WriteLine();
// Chunk 1:
// 0
// 1
// 2
//
//Chunk 2:
// 3
// 4
// 5
//
//Chunk 3:
// 6
// 7
El código de C# anterior:
Consulte también
System.Linq
Información general sobre operadores de consulta estándar (C#)
Operaciones Join (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 4 minutos
Los métodos de combinación que se han proporcionado en el marco de LINQ son Join y
GroupJoin. Estos métodos efectúan combinaciones de igualdad, o combinaciones que
hacen corresponder dos orígenes de datos en función de la igualdad de sus claves. (Para
comparar, Transact-SQL admite otros operadores de combinación aparte de 'igual', por
ejemplo, 'menor que'). En términos de base de datos relacional, Join implementa una
combinación interna, un tipo de combinación en la que solo se devuelven los objetos
que tienen una correspondencia en el otro conjunto de datos. El método GroupJoin no
tiene equivalente directo en términos de bases de datos relacionales; pero implementa
un superconjunto de combinaciones internas y combinaciones externas izquierdas. Una
combinación externa izquierda es una combinación que devuelve cada elemento del
primer origen de datos (izquierda), aunque no tenga elementos correlacionados en el
otro origen de datos.
Join
En el ejemplo siguiente se usa la cláusula join … in … on … equals … para combinar dos
secuencias en función de un valor específico:
C#
class Product
class Category
};
Console.WriteLine($"{item.Name} - {item.CategoryName}");
//
// Cola - Beverage
// Tea - Beverage
// Apple - Fruit
// Kiwi - Fruit
// Carrot - Vegetable
GroupJoin
En el ejemplo siguiente se usa la cláusula join … in … on … equals … into … para
combinar dos secuencias en función de un valor específico y se agrupan las
coincidencias resultantes de cada elemento:
C#
class Product
class Category
};
select productGroup;
Console.WriteLine("Group");
Console.WriteLine($"{product.Name,8}");
//
// Group
// Cola
// Tea
// Group
// Apple
// Kiwi
// Group
// Carrot
Consulte también
System.Linq
Información general sobre operadores de consulta estándar (C#)
Tipos anónimos
Formular combinaciones Join y consultas entre productos
join (cláusula)
Join usando claves compuestas
Procedimiento para combinar contenido de archivos no similares (LINQ) (C#)
Ordenar los resultados de una cláusula join
Realizar operaciones de combinación personalizadas
Realizar combinaciones agrupadas
Realizar combinaciones internas
Realizar operaciones de combinación externa izquierda
Procedimiento para rellenar colecciones de objetos de varios orígenes (LINQ) (C#)
Agrupar datos (C#)
Artículo • 29/11/2022 • Tiempo de lectura: 2 minutos
Métodos
Nombre Descripción Sintaxis de la Más información
del expresión de
método consulta de
C#
C#
List<int> numbers = new List<int>() { 35, 44, 200, 84, 3987, 4, 199, 329,
446, 208 };
Console.WriteLine(i);
Odd numbers:
35
3987
199
329
Even numbers:
44
200
84
446
208
*/
Consulte también
System.Linq
Información general sobre operadores de consulta estándar (C#)
group (cláusula)
Crear grupos anidados
Procedimiento para agrupar archivos por extensión (LINQ) (C#)
Agrupar los resultados de consultas
Realizar una subconsulta en una operación de agrupación
Procedimiento para dividir un archivo en varios mediante el uso de grupos (LINQ)
(C#)
Operaciones de generación (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Los métodos del operador de consulta estándar que realizan generaciones se indican en
la sección siguiente.
Métodos
Nombre del Descripción Sintaxis de la Más información
método expresión de
consulta de C#
Consulte también
System.Linq
Información general sobre operadores de consulta estándar (C#)
Operaciones de igualdad (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Dos secuencias cuyos respectivos elementos sean iguales y que tengan el mismo
número de elementos se consideran iguales.
Métodos
Nombre del Descripción Sintaxis de la Más información
método expresión de
consulta de C#
Consulte también
System.Linq
Información general sobre operadores de consulta estándar (C#)
Procedimiento para comparar el contenido de dos carpetas (LINQ) (C#)
Operaciones de elementos (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Los métodos del operador de consulta estándar que realizan operaciones de elementos
se indican en la sección siguiente.
Métodos
Nombre del Descripción Sintaxis Información adicional
método de la
expresión
de
consulta
de C#
Consulte también
System.Linq
Información general sobre operadores de consulta estándar (C#)
Procedimiento para buscar el archivo o archivos de mayor tamaño en un árbol de
directorios (LINQ) (C#)
Convertir tipos de datos (C#)
Artículo • 22/09/2022 • Tiempo de lectura: 2 minutos
Las operaciones de conversión en las consultas LINQ son útiles en una serie de
aplicaciones. A continuación se muestran algunos ejemplos:
Métodos
En la siguiente tabla se muestran los métodos de operadores de consulta estándar que
efectúan conversiones de tipo de datos.
Los métodos de conversión de esta tabla cuyos nombres comienzan por "As" cambian el
tipo estático de la colección de origen, pero no lo enumeran. Los métodos cuyos
nombres empiezan por "To" enumeran la colección de origen y colocan los elementos
en el tipo de colección correspondiente.
from string
str in
words
C#
class Plant
};
select cPlant;
Console.WriteLine(plant.Name);
Waterwheel Plant
*/
Consulte también
System.Linq
Información general sobre operadores de consulta estándar (C#)
from (cláusula)
Expresiones de consulta LINQ
Procedimiento para consultar un objeto ArrayList con LINQ (C#)
Operaciones de concatenación (C#)
Artículo • 10/02/2023 • Tiempo de lectura: 2 minutos
Los métodos del operador de consulta estándar que efectúan una concatenación se
indican en la siguiente sección.
Métodos
Nombre del Descripción Sintaxis de la expresión Más información
método de consulta de C#
Consulte también
System.Linq
Información general sobre operadores de consulta estándar (C#)
Procedimiento para combinar y comparar colecciones de cadenas (LINQ) (C#)
Operaciones de agregación (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Una operación de agregación calcula un valor único a partir de una colección de valores.
Un ejemplo de operación de agregación es calcular el promedio de temperatura diaria a
partir de los valores de temperatura diaria durante un mes.
Los métodos del operador de consulta estándar que realizan operaciones de agregación
se indican en la sección siguiente.
Métodos
Nombre description Sintaxis de la Información adicional
del expresión de
método consulta de C#
de valores. Queryable.Average
Queryable.Max
Queryable.MaxBy
Queryable.Min
Queryable.MinBy
colección. Queryable.Sum
Consulte también
System.Linq
Información general sobre operadores de consulta estándar (C#)
Procedimiento para calcular valores de columna en un archivo de texto CSV (LINQ)
(C#)
Procedimiento para buscar el archivo o archivos de mayor tamaño en un árbol de
directorios (LINQ) (C#)
Procedimiento para buscar el número total de bytes de un conjunto de carpetas
(LINQ) (C#)
LINQ to Objects (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
El término "LINQ to Objects" se refiere al uso de consultas LINQ con cualquier colección
IEnumerable o IEnumerable<T> directamente, sin usar un proveedor o una API de LINQ
intermedios como LINQ to SQL o LINQ to XML. Puede usar LINQ para consultar
cualquier colección enumerable, como List<T>, Array o Dictionary<TKey,TValue>. La
colección puede haberla definido el usuario, o bien puede que la haya devuelto una API
de .NET.
Además, las consultas LINQ ofrecen tres ventajas principales respecto a los bucles
foreach tradicionales:
Por lo general, cuanto más compleja es la operación que se quiere realizar en los datos,
más ventajas se obtienen al usar LINQ en lugar de las técnicas de iteración tradicionales.
En esta sección
LINQ y cadenas (C#)
Explica cómo se puede usar LINQ para consultar y transformar cadenas y colecciones de
cadenas. También incluye vínculos a artículos que muestran estos principios.
Explica cómo se puede usar LINQ para interactuar con sistemas de archivos. También
incluye vínculos a artículos que muestran estos conceptos.
Procedimiento para agregar métodos personalizados para las consultas LINQ (C#)
Explica cómo extender el conjunto de métodos que puede usar para consultas LINQ
agregando métodos de extensión a la interfaz IEnumerable<T>.
Proporciona vínculos a artículos que explican LINQ e incluye ejemplos de código que
realizan consultas.
LINQ y cadenas (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
También puede usar las técnicas descritas en esta sección para transformar datos de
texto semiestructurados en XML. Para más información, consulte Procedimiento para
generar XML a partir de archivos CSV (C#).
Procedimiento para buscar la diferencia de conjuntos entre dos listas (LINQ) (C#)
Muestra cómo encontrar todas las cadenas que se encuentran en una lista pero no
en la otra.
Procedimiento para ordenar o filtrar datos de texto por palabra o campo (LINQ)
(C#)
Muestra cómo cambiar el orden de los campos en una línea en un archivo .csv.
Muestra cómo combinar cadenas de dos listas en una sola mediante una clave
coincidente.
Muestra cómo crear nuevos archivos mediante un solo archivo como origen de
datos.
Procedimiento para calcular valores de columna en un archivo de texto CSV (LINQ)
(C#)
Vea también
Language Integrated Query (LINQ) (C#)
Procedimiento para generar XML a partir de archivos CSV
Procedimiento para realizar un recuento
de las repeticiones de una palabra en
una cadena (LINQ) (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
En este ejemplo se muestra cómo usar una consulta LINQ para contar las apariciones de
una palabra determinada en una cadena. Observe que para realizar el recuento, primero
se llama al método Split para crear una matriz de palabras. Existe un costo de
rendimiento en el método Split. Si la única operación de la cadena es para contar las
palabras, debe considerar la posibilidad de usar en su lugar los métodos Matches o
IndexOf. Pero si el rendimiento no es un problema crítico, o si ya ha dividido la frase
para realizar otros tipos de consultas, tiene sentido usar LINQ para además contar las
palabras o frases.
Ejemplo
C#
class CountWords
@" and also in SQL or XQuery. On the one side are concepts such as
classes," +
@" objects, fields, inheritance, and .NET APIs. On the other side"
+
@" are tables, columns, rows, nodes, and separate languages for
dealing with" +
@" them. Data types often require translation between the two
worlds; there are" +
string[] source = text.Split(new char[] { '.', '?', '!', ' ', ';',
':', ',' }, StringSplitOptions.RemoveEmptyEntries);
where word.Equals(searchTerm,
StringComparison.InvariantCultureIgnoreCase)
select word;
Console.ReadKey();
/* Output:
*/
Compilar el código
Cree un proyecto de aplicación de consola de C# con directivas using para los espacios
de nombres System.Linq y System.IO.
Consulte también
LINQ y cadenas (C#)
Procedimiento para buscar frases que
contengan un conjunto especificado de
palabras (LINQ) (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
En este ejemplo se muestra cómo buscar frases en un archivo de texto que contengan
coincidencias con cada uno de los conjuntos de palabras especificados. Aunque la
matriz de términos de búsqueda está codificada de forma rígida en este ejemplo,
también se podría rellenar dinámicamente en tiempo de ejecución. En este ejemplo, la
consulta devuelve las frases que contienen las palabras "Historically", "data" e
"integrated".
Ejemplo
C#
class FindSentences
@"and also in SQL or XQuery. On the one side are concepts such as
classes, " +
@"objects, fields, inheritance, and .NET APIs. On the other side " +
@"them. Data types often require translation between the two worlds;
there are " +
StringSplitOptions.RemoveEmptyEntries)
where
w.Distinct().Intersect(wordsToMatch).Count() == wordsToMatch.Count()
select sentence;
Console.WriteLine(str);
Console.ReadKey();
/* Output:
Historically, the world of data and the world of objects have not been well
integrated
*/
La consulta primero divide el texto en frases y, luego, divide las frases en una matriz de
cadenas que contienen cada palabra. Para cada una de estas matrices, el método
Distinct quita todas las palabras duplicadas y, después, la consulta realiza una operación
Intersect en la matriz de palabras y en la matriz wordsToMatch . Si el recuento de la
intersección es igual que el recuento de la matriz wordsToMatch , se han encontrado
todas las palabras y se devuelve la frase original.
En la llamada a Split, los signos de puntuación se usan como separadores para quitar las
frases de la cadena. Si no lo hiciera podría tener, por ejemplo, una cadena "Historically",
lo que no coincidiría con el "Historically" de la matriz wordsToMatch . Podría tener que
usar separadores adicionales, en función de los tipos de puntuación del texto de origen.
Compilar el código
Cree un proyecto de aplicación de consola de C# con directivas using para los espacios
de nombres System.Linq y System.IO.
Consulte también
LINQ y cadenas (C#)
Procedimiento para buscar caracteres en
una cadena (LINQ) (C#)
Artículo • 11/02/2023 • Tiempo de lectura: 2 minutos
Ejemplo
En el ejemplo siguiente se consulta una cadena para determinar el número de dígitos
numéricos que contiene. Tenga en cuenta que la consulta se "reutiliza" después de que
se ejecute la primera vez. Esto es posible porque la propia consulta no almacena ningún
resultado real.
C#
class QueryAString
IEnumerable<char> stringQuery =
from ch in aString
where Char.IsDigit(ch)
select ch;
Console.Write(c);
Console.ReadKey();
/* Output:
Output: 9 9 7 4 1 2 8 9
Count = 8
ABCDE99F
*/
Compilar el código
Cree un proyecto de aplicación de consola de C# con directivas using para los espacios
de nombres System.Linq y System.IO.
Vea también
LINQ y cadenas (C#)
Procedimiento para combinar consultas LINQ con expresiones regulares (C#)
Procedimiento para combinar consultas
LINQ con expresiones regulares (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
En este ejemplo se muestra cómo usar la clase Regex para crear una expresión regular
para coincidencias más complejas en cadenas de texto. Con la consulta LINQ, resulta
fácil filtrar por los archivos exactos que se quieren buscar con la expresión regular y dar
forma a los resultados.
Ejemplo
C#
class QueryWithRegEx
System.Text.RegularExpressions.Regex searchTerm =
new System.Text.RegularExpressions.Regex(@"Visual
(Basic|C#|C\+\+|Studio)");
var queryMatchingFiles =
select new
name = file.FullName,
select match.Value
};
Console.WriteLine(s);
Console.ReadKey();
if (!System.IO.Directory.Exists(path))
files.Add(new System.IO.FileInfo(name));
return files;
Tenga en cuenta que también puede consultar el objeto MatchCollection devuelto por
una búsqueda RegEx . En este ejemplo se genera solo el valor de cada coincidencia en
los resultados. Pero también es posible usar LINQ para realizar todo tipo de filtrado,
ordenación y agrupación en esa colección. Dado que MatchCollection no es una
colección genérica IEnumerable, tendrá que indicar explícitamente el tipo de la variable
de rango en la consulta.
Compilar el código
Cree un proyecto de aplicación de consola de C# con directivas using para los espacios
de nombres System.Linq y System.IO.
Consulte también
LINQ y cadenas (C#)
LINQ y directorios de archivos (C#)
Procedimiento para buscar la diferencia
de conjuntos entre dos listas (LINQ) (C#)
Artículo • 04/01/2023 • Tiempo de lectura: 2 minutos
En este ejemplo se muestra cómo usar LINQ para comparar dos listas de cadenas y
generar estas líneas, que están en names1.txt pero no en names2.txt.
Ejemplo
C#
class CompareLists
string[] names1 =
System.IO.File.ReadAllLines(@"../../../names1.txt");
string[] names2 =
System.IO.File.ReadAllLines(@"../../../names2.txt");
// Create the query. Note that method syntax must be used here.
IEnumerable<string> differenceQuery =
names1.Except(names2);
Console.WriteLine(s);
// Keep the console window open until the user presses a key.
Console.ReadKey();
/* Output:
Potra, Cristina
Noriega, Fabricio
Toyoshima, Tim
Garcia, Debra
*/
Algunos tipos de operaciones de consulta en C#, como Except, Distinct, Union y Concat,
solo pueden expresarse en una sintaxis basada en método.
Compilar el código
Cree un proyecto de aplicación de consola de C# con directivas using para los espacios
de nombres System.Linq y System.IO.
Consulte también
LINQ y cadenas (C#)
Procedimiento para ordenar o filtrar
datos de texto por palabra o campo
(LINQ) (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Ejemplo
C#
string[] scores =
System.IO.File.ReadAllLines(@"../../../scores.csv");
int sortField = 1;
Console.WriteLine(str);
Console.ReadKey();
select line;
return scoreQuery;
*/
En este ejemplo también se muestra cómo devolver una variable de consulta desde un
método.
Compilar el código
Cree un proyecto de aplicación de consola de C# con directivas using para los espacios
de nombres System.Linq y System.IO.
Consulte también
LINQ y cadenas (C#)
Procedimiento para reordenar los
campos de un archivo delimitado (LINQ)
(C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Un archivo de valores separados por comas (CSV) es un archivo de texto que se usa a
menudo para almacenar datos de hoja de cálculo u otros datos tabulares que se
representan mediante filas y columnas. Mediante el uso del método Split para separar
los campos, es muy fácil consultar y manipular los archivos CSV con LINQ. De hecho, la
misma técnica puede usarse para reordenar los elementos de cualquier línea
estructurada de texto, no se limita a un archivo CSV.
csv
Adams,Terry,120
Fakhouri,Fadi,116
Feng,Hanying,117
Garcia,Cesar,114
Garcia,Debra,115
Garcia,Hugo,118
Mortensen,Sven,113
O'Donnell,Claire,112
Omelchenko,Svetlana,111
Tucker,Lance,119
Tucker,Michael,122
Zabokritski,Eugene,121
Ejemplo
C#
class CSVFiles
string[] lines =
System.IO.File.ReadAllLines(@"../../../spreadsheet1.csv");
IEnumerable<string> query =
let x = line.Split(',')
orderby x[2]
// Execute the query and write out the new file. Note that
WriteAllLines
System.IO.File.WriteAllLines(@"../../../spreadsheet2.csv",
query.ToArray());
Console.ReadKey();
/* Output to spreadsheet2.csv:
*/
Compilar el código
Cree un proyecto de aplicación de consola de C# con directivas using para los espacios
de nombres System.Linq y System.IO.
Consulte también
LINQ y cadenas (C#)
LINQ y directorios de archivos (C#)
Procedimiento para generar XML a partir de archivos CSV (C#)
Procedimiento para combinar y
comparar colecciones de cadenas
(LINQ) (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
En este ejemplo se muestra cómo combinar archivos que contienen líneas de texto y
después ordenar los resultados. En concreto, se muestra cómo realizar una
concatenación simple, una unión y una intersección en los dos conjuntos de líneas de
texto.
text
Bankov, Peter
Holm, Michael
Garcia, Hugo
Potra, Cristina
Noriega, Fabricio
Beebe, Ann
Toyoshima, Tim
Garcia, Debra
text
Liu, Jinghao
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Gilchrist, Beth
Myrcha, Jacek
Giakoumakis, Leo
McLin, Nkenge
El Yassir, Mehdi
Ejemplo
C#
class MergeStrings
string[] fileA =
System.IO.File.ReadAllLines(@"../../../names1.txt");
string[] fileB =
System.IO.File.ReadAllLines(@"../../../names2.txt");
IEnumerable<string> concatQuery =
IEnumerable<string> uniqueNamesQuery =
IEnumerable<string> commonNamesQuery =
fileA.Intersect(fileB);
IEnumerable<String> tempQuery1 =
let n = name.Split(',')
select name;
IEnumerable<string> tempQuery2 =
let n2 = name2.Split(',')
select name2;
IEnumerable<string> nameMatchQuery =
Console.ReadKey();
Console.WriteLine(System.Environment.NewLine + message);
Console.WriteLine(item);
/* Output:
Bankov, Peter
Bankov, Peter
Beebe, Ann
Beebe, Ann
El Yassir, Mehdi
Garcia, Debra
Garcia, Hugo
Garcia, Hugo
Giakoumakis, Leo
Gilchrist, Beth
Holm, Michael
Holm, Michael
Liu, Jinghao
McLin, Nkenge
Myrcha, Jacek
Noriega, Fabricio
Potra, Cristina
Toyoshima, Tim
Bankov, Peter
Beebe, Ann
El Yassir, Mehdi
Garcia, Debra
Garcia, Hugo
Giakoumakis, Leo
Gilchrist, Beth
Holm, Michael
Liu, Jinghao
McLin, Nkenge
Myrcha, Jacek
Noriega, Fabricio
Potra, Cristina
Toyoshima, Tim
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Garcia, Debra
Garcia, Hugo
Garcia, Hugo
*/
Compilar el código
Cree un proyecto de aplicación de consola de C# con directivas using para los espacios
de nombres System.Linq y System.IO.
Consulte también
LINQ y cadenas (C#)
LINQ y directorios de archivos (C#)
Procedimiento para rellenar colecciones
de objetos de varios orígenes (LINQ)
(C#)
Artículo • 15/02/2023 • Tiempo de lectura: 4 minutos
7 Nota
No intente unir datos en memoria o datos del sistema de archivos con datos que
todavía están en una base de datos. Dichas combinaciones entre dominios pueden
producir resultados indefinidos porque hay diferentes maneras de definir las
operaciones de combinación para las consultas de base de datos y otros tipos de
orígenes. Además, existe el riesgo de que esta operación produzca una excepción
de memoria insuficiente si la cantidad de datos existente en la base de datos es
considerable. Para combinar datos de una base de datos con datos en memoria,
primero debe llamar a ToList o a ToArray en la base de datos de consulta y, luego,
debe efectuar la combinación en la colección devuelta.
Ejemplo
En el ejemplo siguiente se muestra cómo usar un tipo Student con nombre para
almacenar los datos combinados de dos colecciones de cadenas en memoria que
simulan datos de hoja de cálculo en formato .csv. La primera colección de cadenas
representa los nombres y los identificadores de los estudiantes, mientras que la segunda
colección representa el identificador de los estudiantes (en la primera columna) y cuatro
notas de exámenes. El identificador se usa como clave externa.
C#
using System;
using System.Collections.Generic;
using System.Linq;
class Student
class PopulateCollection
string[] scores =
System.IO.File.ReadAllLines(@"../../../scores.csv");
IEnumerable<Student> queryNamesScores =
where Convert.ToInt32(splitName[2]) ==
Convert.ToInt32(splitScoreLine[0])
FirstName = splitName[0],
LastName = splitName[1],
ID = Convert.ToInt32(splitName[2]),
select Convert.ToInt32(scoreAsText)).
ToList()
};
student.FirstName, student.LastName,
student.ExamScores.Average());
Console.ReadKey();
/* Output:
*/
En la cláusula select se usa un inicializador de objeto para crear una instancia de cada
objeto Student nuevo usando los datos de los dos orígenes.
Si no tiene que almacenar los resultados de una consulta, los tipos anónimos pueden
ser más convenientes que los tipos con nombre. Los tipos con nombre son necesarios si
pasa los resultados de la consulta fuera del método en el que se ejecuta la consulta. En
el ejemplo siguiente se ejecuta la misma tarea que en el ejemplo anterior, con la
diferencia de que se usan tipos anónimos en lugar de tipos con nombre:
C#
var queryNamesScores2 =
where Convert.ToInt32(splitName[2]) ==
Convert.ToInt32(splitScoreLine[0])
select new
First = splitName[0],
Last = splitName[1],
select Convert.ToInt32(scoreAsText))
.ToList()
};
Consulte también
LINQ y cadenas (C#)
Inicializadores de objeto y colección
Tipos anónimos
Procedimiento para dividir un archivo
en muchos mediante grupos (LINQ) (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
text
Bankov, Peter
Holm, Michael
Garcia, Hugo
Potra, Cristina
Noriega, Fabricio
Beebe, Ann
Toyoshima, Tim
Garcia, Debra
text
Liu, Jinghao
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Gilchrist, Beth
Myrcha, Jacek
Giakoumakis, Leo
McLin, Nkenge
El Yassir, Mehdi
Ejemplo
C#
class SplitWithGroups
string[] fileA =
System.IO.File.ReadAllLines(@"../../../names1.txt");
string[] fileB =
System.IO.File.ReadAllLines(@"../../../names2.txt");
let n = name.Split(',')
orderby g.Key
select g;
// Output to display.
Console.WriteLine(g.Key);
// Write file.
sw.WriteLine(item);
Console.ReadKey();
/* Output:
Bankov, Peter
Beebe, Ann
El Yassir, Mehdi
Garcia, Hugo
Garcia, Debra
Gilchrist, Beth
Giakoumakis, Leo
Holm, Michael
Liu, Jinghao
Myrcha, Jacek
McLin, Nkenge
Noriega, Fabricio
Potra, Cristina
Toyoshima, Tim
*/
El programa escribe un archivo independiente para cada grupo en la misma carpeta que
los archivos de datos.
Compilar el código
Cree un proyecto de aplicación de consola de C# con directivas using para los espacios
de nombres System.Linq y System.IO.
Consulte también
LINQ y cadenas (C#)
LINQ y directorios de archivos (C#)
Procedimiento para combinar contenido
de archivos no similares (LINQ) (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
En este ejemplo se muestra cómo combinar datos de dos archivos delimitados por
comas que comparten un valor común que se usa como clave coincidente. Esta técnica
puede ser útil si tiene que combinar datos de dos hojas de cálculo o si tiene que
combinar en un archivo nuevo datos procedentes de una hoja de cálculo y de un
archivo que tiene otro formato. Puede modificar el ejemplo para adaptarlo a cualquier
tipo de texto estructurado.
csv
csv
Omelchenko,Svetlana,111
O'Donnell,Claire,112
Mortensen,Sven,113
Garcia,Cesar,114
Garcia,Debra,115
Fakhouri,Fadi,116
Feng,Hanying,117
Garcia,Hugo,118
Tucker,Lance,119
Adams,Terry,120
Zabokritski,Eugene,121
Tucker,Michael,122
Ejemplo
C#
using System;
using System.Collections.Generic;
using System.Linq;
class JoinStrings
// matching key.
string[] scores =
System.IO.File.ReadAllLines(@"../../../scores.csv");
// Omelchenko, Svetlana, 11
IEnumerable<string> scoreQuery1 =
from id in scores
where Convert.ToInt32(nameFields[2]) ==
Convert.ToInt32(scoreFields[0])
Console.ReadKey();
Console.WriteLine(System.Environment.NewLine + message);
Console.WriteLine(item);
/* Output:
*/
Consulte también
LINQ y cadenas (C#)
LINQ y directorios de archivos (C#)
Procedimiento para calcular valores de
columna en un archivo de texto CSV
(LINQ) (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
En este ejemplo se muestra cómo efectuar cálculos agregados (como sumas, promedios,
mínimos y máximos) en las columnas de un archivo .csv. Los principios de ejemplo que
se muestran aquí se pueden aplicar a otros tipos de textos estructurados.
csv
Ejemplo
C#
class SumColumns
string[] lines =
System.IO.File.ReadAllLines(@"../../../scores.csv");
int exam = 3;
// Spreadsheet format:
Console.WriteLine();
MultiColumns(lines);
Console.ReadKey();
// of strings,
var columnQuery =
select Convert.ToInt32(elements[examNum]);
// using 'var'.
// of strings,
// 2) use Skip to skip the "Student ID" column, and store the
// in the results.
IEnumerable<IEnumerable<int>> multiColQuery =
select Convert.ToInt32(str));
// performance.
// of scores.
select row.ElementAt(column);
/* Output:
*/
La consulta funciona usando el método Split para convertir cada línea de texto en una
matriz. Cada elemento de matriz representa una columna. Por último, el texto de cada
columna se convierte en su representación numérica. Si el archivo es un archivo
separado por tabulaciones, actualice el argumento del método Split a \t .
Compilar el código
Cree un proyecto de aplicación de consola de C# con directivas using para los espacios
de nombres System.Linq y System.IO.
Consulte también
LINQ y cadenas (C#)
LINQ y directorios de archivos (C#)
Procedimiento para consultar los
metadatos de un ensamblado con
reflexión (LINQ) (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Las API de reflexión de .NET Framework se pueden usar para examinar los metadatos de
un ensamblado .NET y para crear colecciones de tipos, escribir miembros, parámetros y
otros elementos que se encuentren en ese ensamblado. Dado que estas colecciones
admiten la interfaz genérica IEnumerable<T>, se pueden consultar mediante LINQ.
En el ejemplo siguiente se muestra cómo se puede usar LINQ con reflexión para
recuperar metadatos concretos sobre métodos que coinciden con un criterio de
búsqueda especificado. En este caso, la consulta encontrará los nombres de todos los
métodos del ensamblado que devuelven tipos enumerables, como matrices.
Ejemplo
C#
using System;
using System.Linq;
using System.Reflection;
class ReflectionHowTO
where type.IsPublic
|| ( method.ReturnType.GetInterface(
typeof(System.Collections.Generic.IEnumerable<>).FullName ) != null
Console.ReadKey();
Vea también
LINQ to Objects (C#)
Procedimiento para consultar los
metadatos de un ensamblado con
reflexión (LINQ) (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Las API de reflexión de .NET Framework se pueden usar para examinar los metadatos de
un ensamblado .NET y para crear colecciones de tipos, escribir miembros, parámetros y
otros elementos que se encuentren en ese ensamblado. Dado que estas colecciones
admiten la interfaz genérica IEnumerable<T>, se pueden consultar mediante LINQ.
En el ejemplo siguiente se muestra cómo se puede usar LINQ con reflexión para
recuperar metadatos concretos sobre métodos que coinciden con un criterio de
búsqueda especificado. En este caso, la consulta encontrará los nombres de todos los
métodos del ensamblado que devuelven tipos enumerables, como matrices.
Ejemplo
C#
using System;
using System.Linq;
using System.Reflection;
class ReflectionHowTO
where type.IsPublic
|| ( method.ReturnType.GetInterface(
typeof(System.Collections.Generic.IEnumerable<>).FullName ) != null
Console.ReadKey();
Vea también
LINQ to Objects (C#)
LINQ y directorios de archivos (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Las consultas en esta sección no son destructivas. No se usan para cambiar el contenido
de las carpetas o los archivos originales. Esto sigue la regla de que las consultas no
deben causar efectos secundarios. En general, cualquier código (incluidas las consultas
que ejecutan operadores de creación actualización y eliminación) que modifica los datos
de origen se debe separar del código que solo consulta los datos.
Muestra cómo devolver el número total de bytes en todos los archivos en un árbol de
directorio especificado.
Muestra cómo devolver todos los archivos que se encuentran en dos carpetas
especificadas y también todos los archivos que se encuentran en una carpeta pero no
en la otra.
Muestra cómo agrupar todos los nombres de archivo que aparecen en más de una
ubicación en un árbol de directorio especificado. También muestra cómo realizar
comparaciones más complejas basadas en un comparador personalizado.
Consulta del contenido de los archivos de una carpeta (LINQ) (C#)
Muestra cómo recorrer en iteración las carpetas de un árbol, abrir cada archivo y
consultar el contenido del archivo.
Comentarios
Hay cierta complejidad en la creación de un origen de datos que representa de forma
precisa el contenido del sistema de archivos y controla las excepciones correctamente.
En los ejemplos de esta sección se crea una colección de instantáneas de objetos
FileInfo que representa todos los archivos en una carpeta raíz especificada y todas sus
subcarpetas. El estado real de cada FileInfo puede cambiar en el periodo comprendido
entre el comienzo y el fin de la ejecución de una consulta. Por ejemplo, se puede crear
una lista de objetos FileInfo para usarla como origen de datos. Si se intenta tener acceso
a la propiedad Length en una consulta, el objeto FileInfo intentará tener acceso al
sistema de archivos para actualizar el valor de Length . Si el archivo ya no existe, se
obtendrá una excepción FileNotFoundException en la consulta, aunque no se esté
consultando el sistema de archivos directamente. Algunas consultas de esta sección
usan un método independiente que consume estas excepciones concretas en casos
determinados. Otra opción consiste en mantener actualizado el origen de datos de
manera dinámica mediante FileSystemWatcher.
Vea también
LINQ to Objects (C#)
Procedimiento para buscar archivos con
un nombre o atributo especificados (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
En este ejemplo se muestra cómo encontrar todos los archivos con una determinada
extensión de nombre de archivo (por ejemplo, ".txt") en un árbol de directorios
especificado. También se muestra cómo devolver el archivo más reciente o más antiguo
del árbol por fecha de creación.
Ejemplo
C#
class FindFileByExtension
// This query will produce the full path for all .txt files
IEnumerable<System.IO.FileInfo> fileQuery =
orderby file.Name
select file;
Console.WriteLine(fi.FullName);
var newestFile =
orderby file.CreationTime
.Last();
newestFile.FullName, newestFile.CreationTime);
Console.ReadKey();
Compilar el código
Cree un proyecto de aplicación de consola de C# con directivas using para los espacios
de nombres System.Linq y System.IO.
Vea también
LINQ to Objects (C#)
LINQ y directorios de archivos (C#)
Procedimiento para agrupar archivos
por extensión (LINQ) (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
En este ejemplo se muestra cómo se puede usar LINQ para efectuar operaciones
avanzadas de agrupación y ordenación en listas de archivos o de carpetas. También
muestra cómo paginar la salida en la ventana de consola mediante los métodos Skip y
Take.
Ejemplo
En la siguiente consulta se muestra cómo agrupar el contenido de un árbol de directorio
especificado por la extensión de nombre de archivo.
C#
class GroupByExtension
// This query will sort all the files under the specified folder
var queryGroupByExt =
orderby fileGroup.Key
select fileGroup;
PageOutput(trimLength, queryGroupByExt);
// It can be modified to work for any long listings of data. Note that
explicit typing
IEnumerable<System.Linq.IGrouping<string, System.IO.FileInfo>>
groupByExtList)
// "3" = 1 line for extension + 1 for "Press any key" + 1 for input
cursor.
int currentLine = 0;
do
Console.Clear();
Console.WriteLine("\t{0}",
f.FullName.Substring(rootLength));
currentLine += numLines;
if (key == ConsoleKey.End)
goAgain = false;
break;
if (goAgain == false)
break;
La salida de este programa puede ser larga, dependiendo de los detalles del sistema de
archivos local y de la configuración de startFolder . Para habilitar la visualización de
todos los resultados, en este ejemplo se muestra cómo paginar los resultados. Se
pueden aplicar las mismas técnicas a las aplicaciones web y Windows. Observe que,
como el código pagina los elementos en un grupo, se necesita un bucle foreach
anidado. También hay alguna lógica adicional para calcular la posición actual en la lista y
para permitir que el usuario detenga la paginación y salga del programa. En este caso
en concreto, la consulta de paginación se ejecuta en los resultados almacenados en
caché de la consulta original. En otros contextos, como en LINQ to SQL, este
almacenamiento en caché no es necesario.
Compilar el código
Cree un proyecto de aplicación de consola de C# con directivas using para los espacios
de nombres System.Linq y System.IO.
Vea también
LINQ to Objects (C#)
LINQ y directorios de archivos (C#)
Procedimiento para buscar el número
total de bytes de un conjunto de
carpetas (LINQ) (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
En este ejemplo se muestra cómo recuperar el número total de bytes usados por todos
los archivos en una carpeta especificada y en todas sus subcarpetas.
Ejemplo
El método Sum agrega los valores de todos los elementos seleccionados en la cláusula
select . Puede modificar fácilmente esta consulta para recuperar el archivo más grande
C#
class QuerySize
IEnumerable<string> fileList =
System.IO.Directory.GetFiles(startFolder, "*.*",
System.IO.SearchOption.AllDirectories);
select GetFileLength(file);
// Return the total number of bytes in all the files under the
specified folder.
Console.ReadKey();
long retval;
try
retval = fi.Length;
}
catch (System.IO.FileNotFoundException)
retval = 0;
return retval;
Compilar el código
Cree un proyecto de aplicación de consola de C# con directivas using para los espacios
de nombres System.Linq y System.IO.
Vea también
LINQ to Objects (C#)
LINQ y directorios de archivos (C#)
Procedimiento para comparar el
contenido de dos carpetas (LINQ) (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
7 Nota
La clase FileComparer que aparece a continuación muestra cómo usar una clase de
comparador personalizada junto con los operadores de consulta estándar. La clase no
está diseñada para su uso en escenarios reales. Simplemente usa el nombre y la
longitud en bytes de cada archivo para determinar si el contenido de cada una de las
carpetas es idéntico o no. En un escenario real, debería modificar este comparador para
realizar una comprobación de igualdad más rigurosa.
Ejemplo
C#
namespace QueryCompareTwoDirs
class CompareDirs
{
if (areIdentical == true)
else
if (queryCommonFiles.Any())
else
Console.WriteLine(v.FullName);
Console.ReadKey();
class FileCompare :
System.Collections.Generic.IEqualityComparer<System.IO.FileInfo>
public FileCompare() { }
f1.Length == f2.Length);
// hash code.
string s = $"{fi.Name}{fi.Length}";
return s.GetHashCode();
Compilar el código
Cree un proyecto de aplicación de consola de C# con directivas using para los espacios
de nombres System.Linq y System.IO.
Vea también
LINQ to Objects (C#)
LINQ y directorios de archivos (C#)
Procedimiento para buscar el archivo o
archivos de mayor tamaño en un árbol
de directorios (LINQ) (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
Cómo recuperar el archivo de mayor o menor tamaño del objeto FileInfo de una o
más carpetas en una carpeta raíz especificada.
Cómo ordenar los archivos en grupos según su tamaño en bytes, sin incluir los
archivos inferiores a un tamaño especificado.
Ejemplo
El ejemplo siguiente contiene cinco consultas independientes que muestran cómo
consultar y agrupar archivos, en función de su tamaño en bytes. Puede modificar
fácilmente estos ejemplos para basar la consulta en otra propiedad del objeto FileInfo.
C#
class QueryBySize
QueryFilesBySize();
Console.ReadKey();
long maxSize =
select len)
.Max();
startFolder, maxSize);
System.IO.FileInfo longestFile =
select file)
.First();
startFolder, longestFile.FullName,
longestFile.Length);
System.IO.FileInfo smallestFile =
select file).First();
startFolder, smallestFile.FullName,
smallestFile.Length);
// queryTenLargest is an IEnumerable<System.IO.FileInfo>
var queryTenLargest =
select file).Take(10);
var querySizeGroups =
select fileGroup;
Console.WriteLine(filegroup.Key.ToString() + "00000");
long retval;
try
retval = fi.Length;
}
catch (System.IO.FileNotFoundException)
retval = 0;
return retval;
Para devolver uno o más objetos FileInfo completos, la consulta debe examinar cada
uno de ellos en los datos de origen y, después, ordenarlos por el valor de su propiedad
Length. Después, puede devolver el objeto único o la secuencia con la mayor longitud.
Use First para devolver el primer elemento de una lista. Use Take para devolver el primer
número n de elementos. Especifique un criterio de ordenación descendente para
colocar los elementos más pequeños al principio de la lista.
Compilar el código
Cree un proyecto de aplicación de consola de C# con directivas using para los espacios
de nombres System.Linq y System.IO.
Vea también
LINQ to Objects (C#)
LINQ y directorios de archivos (C#)
Procedimiento para buscar archivos
duplicados en un árbol de directorios
(LINQ) (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
A veces, los archivos que tienen el mismo nombre pueden estar en más de una carpeta.
Por ejemplo, en la carpeta de instalación de Visual Studio, hay varias carpetas que tienen
un archivo readme.htm. En este ejemplo se muestra cómo buscar estos nombres de
archivos duplicados en una carpeta raíz especificada. En el segundo ejemplo se muestra
cómo buscar archivos cuyo tamaño y fecha de LastWrite también coinciden.
Ejemplo
C#
class QueryDuplicateFileNames
QueryDuplicates();
// QueryDuplicates2();
Console.ReadKey();
var queryDupNames =
select fileGroup;
PageOutput<string, string>(queryDupNames);
class PortableKey
{
other.Name == this.Name;
return str.GetHashCode();
//
var queryDupFiles =
group file.FullName.Substring(charsToSkip) by
select fileGroup;
int i = queryDupFiles.Count();
PageOutput<PortableKey, string>(queryDupFiles);
// Here the type of the group must be specified explicitly. "var" cannot
// be used in method signatures. This method does not display more than
one
// "3" = 1 line for extension + 1 for "Press any key" + 1 for input
cursor.
int currentLine = 0;
do
Console.Clear();
Console.WriteLine("\t{0}", fileName);
currentLine += numLines;
if (key == ConsoleKey.End)
goAgain = false;
break;
if (goAgain == false)
break;
En la primera consulta se usa una clave simple para determinar una coincidencia; se
buscan archivos que tengan el mismo nombre, pero cuyo contenido podría ser
diferente. En la segunda consulta se usa una clave compuesta para coincidir con tres
propiedades del objeto FileInfo. En esta consulta es mucho más probable que se
encuentren archivos que tienen el mismo nombre y un contenido similar o idéntico.
Compilar el código
Cree un proyecto de aplicación de consola de C# con directivas using para los espacios
de nombres System.Linq y System.IO.
Vea también
LINQ to Objects (C#)
LINQ y directorios de archivos (C#)
Procedimiento para consultar el
contenido de los archivos de texto de
una carpeta (LINQ) (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Ejemplo
C#
class QueryContents
// queryMatchingFiles is an IEnumerable<string>.
var queryMatchingFiles =
where fileText.Contains(searchTerm)
select file.FullName;
Console.WriteLine(filename);
Console.ReadKey();
if (System.IO.File.Exists(name))
fileContents = System.IO.File.ReadAllText(name);
return fileContents;
Compilar el código
Cree un proyecto de aplicación de consola de C# con directivas using para los espacios
de nombres System.Linq y System.IO.
Consulte también
LINQ y directorios de archivos (C#)
LINQ to Objects (C#)
Procedimiento para consultar un objeto
ArrayList con LINQ (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Cuando use LINQ para consultar colecciones no genéricas IEnumerable como ArrayList,
debe declarar explícitamente el tipo de variable de rango para reflejar el tipo específico
de los objetos de la colección. Por ejemplo, si tiene una ArrayList de objetos Student , la
cláusula from debe tener un aspecto similar a este:
C#
//...
El uso de una variable de rango con tipo explícito en una expresión de consulta es
equivalente a llamar al método Cast. Cast genera una excepción si la conversión
especificada no puede realizarse. Cast y OfType son los dos métodos de operador de
consulta estándar que funcionan en tipos IEnumerable no genéricos. Para obtener más
información, vea Relaciones entre tipos en operaciones de consulta LINQ.
Ejemplo
En el siguiente ejemplo se muestra una consulta simple sobre un ArrayList. Tenga en
cuenta que en este ejemplo se usan inicializadores de objeto cuando el código llama al
método Add, pero esto no es un requisito.
C#
using System;
using System.Collections;
using System.Linq;
namespace NonGenericLINQ
class Program
arrList.Add(
new Student
});
arrList.Add(
new Student
});
arrList.Add(
new Student
});
arrList.Add(
new Student
});
select student;
Console.ReadKey();
/* Output:
Omelchenko: 98
Garcia: 97
*/
Vea también
LINQ to Objects (C#)
Procedimiento para agregar métodos
personalizados para las consultas LINQ
(C#)
Artículo • 15/02/2023 • Tiempo de lectura: 5 minutos
Para extender el conjunto de métodos que usa para consultas LINQ, agregue métodos
de extensión a la interfaz IEnumerable<T>. Por ejemplo, además de las operaciones
habituales de promedio o de máximo, puede crear un método de agregación
personalizado para calcular un solo valor a partir de una secuencia de valores. También
puede crear un método que funcione como un filtro personalizado o como una
transformación de datos específica para una secuencia de valores y que devuelva una
nueva secuencia. Ejemplos de dichos métodos son Distinct, Skip y Reverse.
C#
var sortedList =
if (sortedList.Count % 2 == 0)
else
return sortedList[itemIndex];
En el ejemplo de código siguiente se muestra cómo usar el método Median para una
matriz de tipo double .
C#
// int overload
Ahora puede llamar a las sobrecargas Median para los tipos integer y double , como se
muestra en el código siguiente:
C#
int[] numbers2 = { 1, 2, 3, 4, 5 };
// int: Median = 3
En el código siguiente se muestra una sobrecarga del método Median que toma el
delegado Func<T,TResult> como parámetro. Este delegado toma un objeto del tipo
genérico T y devuelve un objeto de tipo double .
C#
// generic overload
Ahora puede llamar al método Median para una secuencia de objetos de cualquier tipo.
Si el tipo no tiene su propia sobrecarga de métodos, deberá pasar un parámetro de
delegado. En C# puede usar una expresión lambda para este propósito. Además, solo
en Visual Basic, si usa la cláusula Aggregate o Group By en lugar de la llamada al
método, puede pasar cualquier valor o expresión que esté en el ámbito de esta cláusula.
En el ejemplo de código siguiente se muestra cómo llamar al método Median para una
matriz de enteros y una matriz de cadenas. Para las cadenas, se calcula la mediana de las
longitudes de las cadenas de la matriz. En el ejemplo se muestra cómo pasar el
parámetro del delegado Func<T,TResult> al método Median para cada caso.
C#
int[] numbers3 = { 1, 2, 3, 4, 5 };
/*
You can use the num => num lambda expression as a parameter for the
Median method
*/
// With the generic overload, you can also use numeric properties of
objects.
// int: Median = 3
// string: Median = 4
int index = 0;
if (index % 2 == 0)
index++;
C#
Console.WriteLine(element);
// a
// c
// e
Consulte también
IEnumerable<T>
Métodos de extensión
LINQ to ADO.NET (Página de portal)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
7 Nota
LINQ to DataSet
DataSet es uno de los componentes más usados de ADO.NET, además de un elemento
clave del modelo de programación desconectado en el que se basa ADO.NET. En
cambio, a pesar de su importancia, DataSet tiene funciones de consulta limitadas.
LINQ to SQL
LINQ to SQL proporciona una infraestructura en tiempo de ejecución para administrar
los datos relacionales como objetos. En LINQ to SQL, el modelo de datos de una base
de datos relacional se asigna a un modelo de objetos expresado en el lenguaje de
programación del programador. Cuando se ejecuta la aplicación, LINQ to SQL convierte
a SQL las consultas integradas en el lenguaje, en el modelo de objetos, y las envía a la
base de datos para su ejecución. Cuando la base de datos devuelve los resultados, LINQ
to SQL los vuelve a convertir en objetos que se pueden manipular.
LINQ to SQL incluye compatibilidad con los procedimientos almacenados y las
funciones definidas por el usuario que están en la base de datos, además de con la
herencia en el modelo de objetos.
LINQ to Entities
A través del modelo Entity Data Model, los datos relacionales se exponen como objetos
en el entorno .NET. Esto hace de la capa de objetos un objetivo idóneo para la
compatibilidad con LINQ, ya que permite a los programadores formular consultas en la
base de datos con el lenguaje usado para compilar la lógica empresarial. Esta
funcionalidad se conoce como LINQ to Entities. Para más información, vea LINQ to
Entities.
Consulte también
LINQ y ADO.NET
Language Integrated Query (LINQ) (C#)
Habilitar un origen de datos para
realizar consultas LINQ
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
Hay varias maneras de extender LINQ para permitir consultar cualquier origen de datos
en el modelo LINQ El origen de datos podría ser una estructura de datos, un servicio
Web, un sistema de archivos o una base de datos, por nombrar algunos. El modelo LINQ
facilita a los clientes las consultas a un origen de datos para el que las consultas LINQ
están habilitadas, ya que la sintaxis y el modelo de consulta no cambian. Las maneras en
las que LINQ se puede extender a estos orígenes de datos son las siguientes:
Datos en memoria
Hay dos maneras de habilitar las consultas LINQ para datos en memoria. Si los datos
son de un tipo que implementa IEnumerable<T>, puede consultar los datos utilizando
LINQ to Objects. Si no tiene sentido habilitar la enumeración de su tipo implementando
la interfaz IEnumerable<T>, puede definir métodos de operador de consulta estándar
de LINQ en ese tipo o crear métodos de operador de consulta estándar de LINQ que
extiendan el tipo. Las implementaciones personalizadas de los operadores de consulta
estándar deberían utilizar ejecución diferida para devolver los resultados.
Datos remotos
La mejor opción para permitir las consultas LINQ para un origen de datos remoto
consiste en implementar la interfaz IQueryable<T>. Sin embargo, esto difiere de
extender un proveedor como LINQ to SQL para un origen de datos.
Vea también
Language Integrated Query (LINQ) (C#)
Reflexión (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
La reflexión proporciona objetos (de tipo Type) que describen los ensamblados,
módulos y tipos. Puede usar la reflexión para crear dinámicamente una instancia de un
tipo, enlazar el tipo a un objeto existente u obtener el tipo desde un objeto existente e
invocar sus métodos, o acceder a sus campos y propiedades. Si usa atributos en el
código, la reflexión le permite acceder a ellos. Para obtener más información, consulte
Attributes (Atributos).
Este es un ejemplo simple de reflexión que usa el método GetType(), heredado por
todos los tipos de la clase base Object , para obtener el tipo de una variable:
7 Nota
C#
int i = 42;
Console.WriteLine(type);
El resultado es System.Int32 .
C#
Console.WriteLine(info);
7 Nota
Las palabras clave de C# protected y internal no tienen ningún significado en
lenguaje intermedio (IL) y no se usan en las API de reflexión. Los términos
correspondientes en IL son Family y Assembly. Para identificar un método internal
con la reflexión, use la propiedad IsAssembly. Para identificar un método protected
internal , use IsFamilyOrAssembly.
Cuando tenga que acceder a atributos en los metadatos del programa. Para
obtener más información, consulte Recuperar información almacenada en
atributos.
Para examinar y crear instancias de tipos en un ensamblado.
Para generar nuevos tipos en tiempo de ejecución. Usar clases en
System.Reflection.Emit.
Para llevar a cabo métodos de acceso de enlace en tiempo de ejecución en tipos
creados en tiempo de ejecución. Consulte el tema Cargar y utilizar tipos
dinámicamente.
Secciones relacionadas
Para obtener más información:
Reflexión
Ver información tipos
Reflexión y tipos genéricos
System.Reflection.Emit
Recuperar la información almacenada en atributos
Consulte también
Guía de programación de C#
Ensamblados de .NET
Serialización (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 4 minutos
Funcionamiento de la serialización
En esta ilustración se muestra el proceso general de la serialización:
El objeto se serializa en una secuencia que incluye los datos. La secuencia también
puede tener información sobre el tipo del objeto, como la versión, la referencia cultural
y el nombre del ensamblado. A partir de esa secuencia, el objeto se puede almacenar en
una base de datos, en un archivo o en memoria.
Usos de la serialización
La serialización permite al desarrollador guardar el estado de un objeto y volver a
crearlo según sea necesario, ya que proporciona almacenamiento de los objetos e
intercambio de datos. A través de la serialización, un desarrollador puede realizar
acciones como las siguientes:
Serialización de JSON
El espacio de nombres System.Text.Json contiene clases para la serialización y
deserialización de notación de objetos JavaScript (JSON). JSON es un estándar abierto
que se usa normalmente para compartir datos en la web.
2 Advertencia
La serialización binaria puede ser peligrosa. Para más información, consulte Guía de
seguridad de BinaryFormatter.
La serialización XML serializa las propiedades y los campos públicos de un objeto o 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 esquema XML (XSD). La
serialización XML produce clases fuertemente tipadas cuyas propiedades y campos
públicos se convierten a XML. System.Xml.Serialization contiene clases para serializar y
deserializar XML. Se aplican atributos a clases y a miembros de clase para controlar la
forma en que XmlSerializer serializa o deserializa una instancia de la clase.
Si una clase serializada contiene referencias a objetos de otras clases marcadas como
SerializableAttribute, esos objetos también se serializarán.
Serialización de diseñador
La serialización de diseñador es una forma especial de serialización que conlleva el tipo
de persistencia de objeto asociado a las herramientas de desarrollo. La serialización de
diseñador es un proceso que consiste en convertir un gráfico de objetos en un archivo
de código fuente que puede utilizarse posteriormente para recuperar el gráfico de
objetos. Un archivo de código fuente puede contener código, marcado o incluso
información de la tabla SQL.
Se explica cómo se puede usar la serialización para conservar los datos de un objeto
entre instancias, lo que le permite almacenar valores y recuperarlos la próxima vez que
se cree una instancia del objeto.
Se muestra cómo leer los datos de objetos que se han escrito anteriormente en un
archivo XML con la clase XmlSerializer.
Se muestra cómo escribir el objeto de una clase en un archivo XML con la clase
XmlSerializer.
Procedimiento para escribir datos de
objeto en un archivo XML (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
En este ejemplo se escribe el objeto de una clase en un archivo XML con la clase
XmlSerializer.
Ejemplo
C#
WriteXML();
System.Xml.Serialization.XmlSerializer writer =
new System.Xml.Serialization.XmlSerializer(typeof(Book));
var path =
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) +
"//SerializationOverview.xml";
writer.Serialize(file, overview);
file.Close();
Compilar el código
La clase que se serializa debe tener un constructor público sin parámetros.
Programación sólida
Las condiciones siguientes pueden provocar una excepción:
Seguridad de .NET
En este ejemplo se crea un nuevo archivo, si este no existe aún. Si una aplicación
necesita crear un archivo, precisará acceso Create para la carpeta. Si el archivo ya existe,
la aplicación necesitará solo acceso Write , un privilegio menor. Siempre que sea
posible, resulta más seguro crear el archivo durante la implementación y conceder solo
acceso Read a un único archivo, en lugar de acceso Create para una carpeta.
Vea también
StreamWriter
Procedimiento para leer datos de objeto de un archivo XML (C#)
Serialización (C#)
Procedimiento para leer datos de objeto
de un archivo XML (C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
En este ejemplo se leen los datos de objetos que se han escrito anteriormente en un
archivo XML con la clase XmlSerializer.
Ejemplo
C#
writer.Serialize(wfile, b);
wfile.Close();
System.Xml.Serialization.XmlSerializer reader =
new System.Xml.Serialization.XmlSerializer(typeof(Book));
@"c:\temp\SerializationOverview.xml");
file.Close();
Console.WriteLine(overview.title);
Compilar el código
Reemplace el nombre de archivo "c:\temp\SerializationOverview.xml" por el nombre del
archivo que contiene los datos serializados. Para más información sobre la serialización
de datos, consulte Procedimiento para escribir datos de objeto en un archivo XML (C#).
Programación sólida
Las condiciones siguientes pueden provocar una excepción:
Los datos del archivo no representan los datos de la clase que se va a deserializar.
Seguridad de .NET
Compruebe siempre las entradas y nunca deserialice datos de un origen que no sea de
confianza. El objeto que se ha vuelto a crear se ejecuta en un equipo local con los
permisos del código que lo ha deserializado. Compruebe todas las entradas antes de
utilizar los datos en la aplicación.
Vea también
StreamWriter
Procedimiento para escribir datos de objeto en un archivo XML (C#)
Serialización (C#)
Guía de programación de C#
Tutorial: Conservar un objeto con C#
Artículo • 17/02/2023 • Tiempo de lectura: 4 minutos
Puede usar la serialización de JSON para conservar los datos de un objeto entre
instancias, lo que le permite almacenar valores y recuperarlos la próxima vez que se cree
una instancia del objeto.
En este tutorial, creará un objeto Loan básico y conservará sus datos en un archivo
JSON. Después, recuperará los datos del archivo cuando vuelva a crear el objeto.
) Importante
En este ejemplo se crea un nuevo archivo, si este no existe aún. Si una aplicación
debe crear un archivo, es necesario que tenga el permiso Create en la carpeta. Los
permisos se establecen mediante el uso de las listas de control de acceso. Si el
archivo ya existe, la aplicación necesitará solo un permiso Write , un permiso
menor. Siempre que sea posible, resulta más seguro crear el archivo durante la
implementación y conceder solo permisos Read a un único archivo (en lugar de
Create permisos para una carpeta). También es más seguro escribir datos en
carpetas de usuario en lugar de hacerlo en la carpeta raíz o en la carpeta Archivos
de programa.
) Importante
Requisitos previos
Para compilar y ejecutar, instale el SDK de .NET .
Sugerencia
1. Cree una aplicación. Escriba dotnet new console -o serialization para crear una
aplicación de consola en un subdirectorio llamado serialization .
C#
[JsonIgnore]
set
_customer = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(Customer)));
double interestRate,
int term,
string customer)
LoanAmount = loanAmount;
InterestRate = interestRate;
Term = term;
_customer = customer;
C#
C#
Console.WriteLine(testLoan.InterestRate);
testLoan.InterestRate = 7.1;
Console.WriteLine(testLoan.InterestRate);
Consola
7.5
7.1
Si ejecuta esta aplicación repetidamente, verá que siempre escribe los mismos valores.
Se creará un objeto Loan cada vez que ejecute el programa. En el mundo real, las tasas
de interés cambian periódicamente, pero no necesariamente cada vez que se ejecuta la
aplicación. El código de serialización conlleva que se va a conservar la tasa de interés
más reciente entre las instancias de la aplicación. En el paso siguiente, hará esto
agregando la serialización a la clase Loan .
C#
[JsonIgnore]
C#
using System.IO;
using System.Text.Json;
2. El siguiente paso es agregar código para deserializar el objeto del archivo cuando
el objeto se crea. Agregue una constante a la clase para el nombre de archivo de
los datos serializados, tal y como se muestra en el siguiente código:
C#
C#
if (File.Exists(fileName))
testLoan = JsonSerializer.Deserialize<Loan>(jsonFromFile);
testLoan.TimeLastLoaded = DateTime.Now;
C#
// Serialize it.
File.WriteAllText(fileName, json);
En este punto, podrá compilar y ejecutar la aplicación de nuevo. La primera vez que se
ejecuta, observe que el tipo de interés comienza en 7.5 y, después, pasa a 7.1. Cierre la
aplicación y, después, ejecútela de nuevo. Ahora, la aplicación muestra un mensaje que
indica que se ha leído el archivo guardado y la tasa de interés es 7.1 incluso antes del
código que la cambia.
Vea también
Serialización (C#)
Guía de programación de C#
Procedimiento para serializar y deserializar JSON en .NET
Instrucciones (Guía de programación de
C#)
Artículo • 14/02/2023 • Tiempo de lectura: 6 minutos
Las acciones que realiza un programa se expresan en instrucciones. Entre las acciones
comunes se incluyen declarar variables, asignar valores, llamar a métodos, recorrer
colecciones en bucle y crear una bifurcación a uno u otro bloque de código, en función
de una condición determinada. El orden en el que se ejecutan las instrucciones en un
programa se denomina flujo de control o flujo de ejecución. El flujo de control puede
variar cada vez que se ejecuta un programa, en función de cómo reacciona el programa
a la entrada que recibe en tiempo de ejecución.
Una instrucción puede constar de una sola línea de código que finaliza en un punto y
coma o de una serie de instrucciones de una sola línea en un bloque. Un bloque de
instrucciones se incluye entre llaves {} y puede contener bloques anidados. En el código
siguiente se muestran dos ejemplos de instrucciones de una sola línea y un bloque de
instrucciones de varias líneas:
C#
// Declaration statement.
int counter;
// Assignment statement.
counter = 1;
// counter + 1;
counter++;
/*
Output:
*/
Tipos de instrucciones
En la tabla siguiente se muestran los distintos tipos de instrucciones de C# y sus
palabras clave asociadas, con vínculos a temas que incluyen más información:
Instrucciones Una instrucción de declaración introduce una variable o constante nueva. Una
de declaración de variable puede asignar opcionalmente un valor a la variable. En
declaración una declaración de constante, se requiere la asignación.
Instrucciones Las instrucciones de expresión que calculan un valor deben almacenar el valor en
de expresión una variable.
Instrucciones Las instrucciones de salto transfieren el control a otra sección de código. Para
de salto obtener más información, vea los temas siguientes:
break
continue
goto
return
yield
Instrucción Si marca un método con el modificador async , puede usar el operador await en el
await método. Cuando el control alcanza una expresión await en el método
asincrónico, el control se devuelve al autor de llamada y el progreso del método
se suspende hasta que se completa la tarea esperada. Cuando se completa la
tarea, la ejecución puede reanudarse en el método.
Instrucción Un iterador realiza una iteración personalizada en una colección, como una lista o
yield matriz. Un iterador utiliza la instrucción yield return para devolver cada elemento
return de uno en uno. Cuando se alcanza una instrucción yield return , se recuerda la
ubicación actual en el código. La ejecución se reinicia desde esa ubicación la
próxima vez que se llama el iterador.
Instrucciones Puede asignar una etiqueta a una instrucción y, después, usar la palabra clave
con etiqueta goto para saltar a la instrucción con etiqueta. (Vea el ejemplo de la línea
siguiente).
Instrucciones de declaración
En el código siguiente se muestran ejemplos de declaraciones de variables con y sin una
asignación inicial, y una declaración constante con la inicialización necesaria.
C#
double area;
double radius = 2;
Instrucciones de expresión
En el código siguiente se muestran ejemplos de instrucciones de expresión, que
incluyen la asignación, la creación de objetos con asignación y la invocación de método.
C#
//circ * 2;
System.Console.WriteLine();
System.Collections.Generic.List<string> strings =
new System.Collections.Generic.List<string>();
Instrucción vacía
En los ejemplos siguientes se muestran dos usos de una instrucción vacía:
C#
void ProcessMessages()
while (ProcessMessage())
void F()
//...
//...
exit:
Instrucciones insertadas
Algunas instrucciones, por ejemplo, las instrucciones de iteración, siempre van seguidas
de una instrucción insertada. Esta instrucción insertada puede ser una sola instrucción o
varias instrucciones incluidas entre llaves {} en un bloque de instrucciones. Las
instrucciones insertadas de una sola línea también pueden ir entre llaves {}, como se
muestra en el siguiente ejemplo:
C#
System.Console.WriteLine(s);
// Not recommended.
System.Console.WriteLine(s);
Una instrucción insertada que no está incluida entre llaves {} no puede ser una
instrucción de declaración o una instrucción con etiqueta. Esto se muestra en el ejemplo
siguiente:
C#
if(pointB == true)
//Error CS1023:
int radius = 5;
C#
if (b == true)
// OK:
System.DateTime d = System.DateTime.Now;
System.Console.WriteLine(d.ToLongDateString());
C#
if (s.StartsWith("CSharp"))
if (s.EndsWith("TempFolder"))
return s;
Instrucciones inaccesibles
Si el compilador determina que el flujo de control no puede alcanzar nunca una
instrucción determinada bajo ninguna circunstancia, producirá una advertencia CS0162,
como se muestra en el ejemplo siguiente:
C#
if (val < 4)
Consulte también
Guía de programación de C#
Palabras clave de instrucciones
Operadores y expresiones de C#
Miembros con cuerpo de expresión
(Guía de programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
C#
Las definiciones del cuerpo de expresiones se pueden usar con los miembros de tipo
siguientes:
Método
Propiedad de solo lectura
Propiedad
Constructor
Finalizador
Indizador
Métodos
Un método con cuerpo de expresión consta de una sola expresión que devuelve un
valor cuyo tipo coincide con el tipo de valor devuelto del método, o bien, para los
métodos que devuelven void , que realiza alguna operación. Por ejemplo, los tipos que
reemplazan el método ToString normalmente incluyen una sola expresión que devuelve
la representación de cadena del objeto actual.
En el ejemplo siguiente se define una clase Person que reemplaza el método ToString
con una definición de cuerpo de expresión. También define un método DisplayName que
muestra un nombre en la consola. Tenga en cuenta que la palabra clave return no se
usa en la definición de cuerpo de expresión de ToString .
C#
using System;
fname = firstName;
lname = lastName;
class Example
Console.WriteLine(p);
p.DisplayName();
C#
En el ejemplo siguiente se define una clase Location cuya propiedad Name de solo
lectura se implementa como una definición de cuerpo de expresión que devuelve el
valor del campo privado locationName :
C#
locationName = name;
Para más información sobre las propiedades, vea Propiedades (Guía de programación
de C#).
Propiedades
Puede usar las definiciones del cuerpo de expresiones para implementar los
descriptores de acceso get y set de propiedades. En el ejemplo siguiente se muestra
cómo hacerlo:
C#
Para más información sobre las propiedades, vea Propiedades (Guía de programación
de C#).
Constructores
Una definición de cuerpo de expresión para un constructor normalmente consta de una
expresión de asignación única o una llamada de método que controla los argumentos
del constructor o inicializa el estado de la instancia.
En el ejemplo siguiente se define una clase Location cuyo constructor tiene un único
parámetro de cadena denominado name. La definición del cuerpo de expresión asigna
el argumento a la propiedad Name .
C#
Finalizadores
Una definición de cuerpo de expresión para un finalizador normalmente contiene
instrucciones de limpieza, como las instrucciones que liberan recursos no administrados.
C#
Indizadores
Como las propiedades, los descriptores de acceso get y set del indizador constan de
las definiciones de cuerpos de expresión si el descriptor de acceso get está formado
por una sola expresión que devuelve un valor o el descriptor de acceso set realiza una
asignación simple.
En el ejemplo siguiente se define una clase denominada Sports que incluye una matriz
String interna que contiene los nombres de varios deportes. Los descriptores de acceso
get y set del indizador se implementan como definiciones de cuerpos de expresión.
C#
using System;
using System.Collections.Generic;
"Volleyball" };
Consulte también
Reglas de estilo de código de .NET para miembros con forma de expresión
Comparaciones de igualdad (guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 4 minutos
A veces es necesario comparar si dos valores son iguales. En algunos casos, se prueba la
igualdad de valores, también denominada equivalencia, lo que significa que los valores
contenidos en las dos variables son iguales. En otros casos, hay que determinar si dos
variables hacen referencia al mismo objeto subyacente de la memoria. Este tipo de
igualdad se denomina igualdad de referencia o identidad. En este tema se describen
estos dos tipos de igualdad y se proporcionan vínculos a otros temas para obtener más
información.
Igualdad de referencia
La igualdad de referencia significa que dos referencias de objeto hacen referencia al
mismo objeto subyacente. Esto puede suceder mediante una asignación simple, como
se muestra en el ejemplo siguiente.
C#
using System;
class Test
// False:
// Assign b to a.
b = a;
// True:
Console.ReadKey();
Igualdad de valores
La igualdad de valores significa que dos objetos contienen el mismo valor o valores.
Para los tipos de valor primitivos, como int o bool, las pruebas de igualdad de valores
son sencillas. Puede usar el operador ==, como se muestra en el ejemplo siguiente.
C#
int a = GetOriginalValue();
int b = GetCurrentValue();
if (b == a)
Para la mayoría de los otros tipos, las pruebas de igualdad de valores son más
complejas, porque es preciso entender cómo la define el tipo. Para las clases y los
structs que tienen varios campos o propiedades, la igualdad de valores suele definirse
de modo que significa que todos los campos o propiedades tienen el mismo valor. Por
ejemplo, podrían definirse dos objetos Point que fueran equivalentes si pointA.X es
igual a pointB.X y pointA.Y es igual a pointB.Y. En el caso de los registros, la igualdad de
valores significa que dos variables de un tipo de registro son iguales si los tipos
coinciden y todos los valores de propiedad y campo coinciden.
En cambio, no hay ningún requisito que exija que la equivalencia se base en todos los
campos de un tipo. Se puede basar en un subconjunto. Al comparar tipos que no sean
de su propiedad, es importante asegurarse concretamente de cómo se define la
equivalencia para ese tipo. Para más información sobre cómo definir la igualdad de
valores en sus propias clases y structs, consulte Procedimiento Definir la igualdad de
valores para un tipo.
Temas relacionados
Title Descripción
Vea también
Guía de programación de C#
Definición de la igualdad de valores
para una clase o una estructura (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 9 minutos
Cuando defina una clase o un struct, debe decidir si tiene sentido crear una definición
personalizada de igualdad (o equivalencia) de valores para el tipo. Normalmente, la
igualdad de valores se implementa cuando se espera agregar objetos del tipo a una
colección, o cuando su objetivo principal es almacenar un conjunto de campos o
propiedades. Puede basar la definición de la igualdad de valores en una comparación de
todos los campos y propiedades del tipo, o bien puede basarla en un subconjunto.
En cualquier caso, tanto en las clases como en las estructuras, la implementación debe
cumplir las cinco garantías de equivalencia (en las siguientes reglas, se da por hecho
que x , y y z no son NULL):
en este artículo.
5. Opcional: Para admitir definiciones para "mayor que" o "menor que", implemente
la interfaz IComparable<T> para el tipo y sobrecargue los operadores <= y >=.
7 Nota
A partir de C# 9.0, puede usar registros para obtener la semántica de igualdad de
valores sin código reutilizable innecesario.
Ejemplo de clase
En el ejemplo siguiente se muestra cómo implementar la igualdad de valores en una
clase (tipo de referencia).
C#
namespace ValueEqualityClass;
this.X = x;
this.Y = y;
if (p is null)
return false;
if (Object.ReferenceEquals(this, p))
return true;
if (this.GetType() != p.GetType())
return false;
if (lhs is null)
if (rhs is null)
return true;
return false;
return lhs.Equals(rhs);
public static bool operator !=(TwoDPoint lhs, TwoDPoint rhs) => !(lhs ==
rhs);
: base(x, y)
this.Z = z;
if (p is null)
return false;
if (Object.ReferenceEquals(this, p))
return true;
if (Z == p.Z)
return base.Equals((TwoDPoint)p);
else
return false;
if (lhs is null)
if (rhs is null)
return true;
return false;
return lhs.Equals(rhs);
class Program
int i = 5;
Console.WriteLine("pointA.Equals(pointB) = {0}",
pointA.Equals(pointB));
Console.WriteLine("pointE.Equals(list[0]): {0}",
pointE.Equals(list[0]));
Console.ReadKey();
/* Output:
pointA.Equals(pointB) = True
pointE.Equals(list[0]): False
*/
Los operadores == y != pueden usarse con clases, incluso si la clase no los sobrecarga,
pero el comportamiento predeterminado consiste en realizar una comprobación de
igualdad de referencia. En una clase, si sobrecarga el método Equals , debería
sobrecargar los operadores == y != , pero no es obligatorio.
) Importante
Ejemplo de estructura
En el ejemplo siguiente se muestra cómo implementar la igualdad de valores en un
struct (tipo de valor):
C#
namespace ValueEqualityStruct
: this()
X = x;
Y = y;
class Program
int i = 5;
// True:
Console.WriteLine("pointA.Equals(pointB) = {0}",
pointA.Equals(pointB));
// True:
// True:
// False:
Console.WriteLine("pointA.Equals(null) = {0}",
pointA.Equals(null));
// False:
// True:
// False:
// CS0019:
// True:
Console.WriteLine("pointA.Equals(list[0]): {0}",
pointA.Equals(list[0]));
// False:
// True:
pointC = temp;
// True:
pointD = temp;
// True:
Console.ReadKey();
/* Output:
pointA.Equals(pointB) = True
pointA.Equals(null) = False
pointA.Equals(i) = False
pointE.Equals(list[0]): True
*/
Consulte también
Comparaciones de igualdad
Guía de programación de C#
Procedimiento Probar la igualdad de
referencias (identidad) (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
No tiene que implementar ninguna lógica personalizada para admitir las comparaciones
de igualdad de referencias en los tipos. Esta funcionalidad se proporciona para todos los
tipos mediante el método Object.ReferenceEquals estático.
Ejemplo
C#
using System.Text;
namespace TestReferenceEquality
struct TestStruct
Num = i;
Name = s;
class TestClass
class Program
#region ReferenceTypes
tcB = tcA;
tcA.Num = 42;
#endregion
#region ValueTypes
#endregion
#region stringRefEquality
// False:
Object.ReferenceEquals(stringC, strB));
#endregion
Console.ReadKey();
/* Output:
*/
El runtime siempre aplica el método Intern a las cadenas constantes dentro del mismo
ensamblado. Es decir, solo se conserva una instancia de cada cadena literal única. Pero
el runtime no garantiza que se vaya a aplicar el método Intern a las cadenas creadas en
tiempo de ejecución, ni tampoco que dicho método se aplique a dos cadenas
constantes iguales en distintos ensamblados.
Consulte también
Comparaciones de igualdad
Conversiones de tipos (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 5 minutos
Dado que C# tiene tipos estáticos en tiempo de compilación, después de declarar una
variable, no se puede volver a declarar ni se le puede asignar un valor de otro tipo a
menos que ese tipo sea convertible de forma implícita al tipo de la variable. Por
ejemplo, string no se puede convertir de forma implícita a int . Por tanto, después de
declarar i como un valor int , no se le puede asignar la cadena "Hello", como se
muestra en el código siguiente:
C#
int i;
i = "Hello";
Pero es posible que en ocasiones sea necesario copiar un valor en una variable o
parámetro de método de otro tipo. Por ejemplo, es posible que tenga una variable de
entero que se necesita pasar a un método cuyo parámetro es de tipo double . O es
posible que tenga que asignar una variable de clase a una variable de tipo de interfaz.
Estos tipos de operaciones se denominan conversiones de tipos. En C#, se pueden
realizar las siguientes conversiones de tipos:
Conversiones con clases del asistente: para realizar conversiones entre tipos no
compatibles, como enteros y objetos System.DateTime, o cadenas hexadecimales y
matrices de bytes puede usar la clase System.BitConverter, la clase System.Convert
y los métodos Parse de los tipos numéricos integrados, como Int32.Parse. Para
obtener más información, consulte Procedimiento Convertir una matriz de bytes
en un valor int, Procedimiento Convertir una cadena en un número y
Procedimiento Convertir cadenas hexadecimales en tipos numéricos.
Conversiones implícitas
Para los tipos numéricos integrados, se puede realizar una conversión implícita cuando
el valor que se va a almacenar se puede encajar en la variable sin truncarse ni
redondearse. Para los tipos enteros, esto significa que el intervalo del tipo de origen es
un subconjunto apropiado del intervalo para el tipo de destino. Por ejemplo, una
variable de tipo long (entero de 64 bits) puede almacenar cualquier valor que un tipo int
(entero de 32 bits) pueda almacenar. En el ejemplo siguiente, el compilador convierte de
forma implícita el valor de num en la parte derecha a un tipo long antes de asignarlo a
bigNum .
C#
Para obtener una lista completa de las conversiones numéricas implícitas, consulte la
sección Conversiones numéricas implícitas del artículo Conversiones numéricas
integradas.
Para los tipos de referencia, siempre existe una conversión implícita desde una clase a
cualquiera de sus interfaces o clases base directas o indirectas. No se necesita ninguna
sintaxis especial porque una clase derivada siempre contiene a todos los miembros de
una clase base.
C#
// Always OK.
Base b = d;
Conversiones explícitas
Pero si no se puede realizar una conversión sin riesgo de perder información, el
compilador requiere que se realice una conversión explícita, que se denomina
conversión. Una conversión de tipos es una manera de informar explícitamente al
compilador de que se pretende realizar la conversión y se es consciente de que se
puede producir pérdida de datos o la conversión de tipos puede fallar en tiempo de
ejecución. Para realizar una conversión, especifique el tipo al que se va a convertir entre
paréntesis delante del valor o la variable que se va a convertir. El siguiente programa
convierte un tipo double en un tipo int. El programa no se compilará sin la conversión.
C#
class Test
double x = 1234.7;
int a;
a = (int)x;
System.Console.WriteLine(a);
// Output: 1234
Para obtener una lista completa de las conversiones numéricas explícitas admitidas,
consulte la sección Conversiones numéricas explícitas del artículo Conversiones
numéricas integradas.
Para los tipos de referencia, se requiere una conversión explícita si es necesario convertir
de un tipo base a un tipo derivado:
C#
Animal a = g;
Giraffe g2 = (Giraffe)a;
C#
class Animal
class UnSafeCast
Test(new Mammal());
System.Console.ReadKey();
}
Reptile r = (Reptile)a;
El método Test tiene un parámetro Animal , por lo que la conversión explícita del
argumento a en un Reptile supone una suposición peligrosa. Es más seguro no hacer
suposiciones, sino comprobar el tipo. C# proporciona el operador is para permitir
probar la compatibilidad antes de realizar una conversión. Para obtener más
información, consulte Procedimiento para convertir de forma segura mediante la
coincidencia de patrones y los operadores is y as.
Vea también
Guía de programación de C#
Tipos
Expresión Cast
Operadores de conversión definidos por el usuario
Conversión de tipos generalizada
Procedimiento Convertir una cadena en un número
Conversión boxing y unboxing (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 6 minutos
C#
int i = 123;
object o = i;
C#
o = 123;
i = (int)o; // unboxing
C#
// String.Concat example.
// List example.
// of elements.
mixedList.Add("First Group:");
// Rest the mouse pointer over j to verify that you are adding
// an int to a list of objects. Each element j is boxed when
mixedList.Add(j);
mixedList.Add("Second Group:");
mixedList.Add(j);
// Rest the mouse pointer over item to verify that the elements
Console.WriteLine(item);
// The following loop sums the squares of the first group of boxed
var sum = 0;
// 'object'.
// Output:
// Answer42True
// First Group:
// 1
// 2
// 3
// 4
// Second Group:
// 5
// 6
// 7
// 8
// 9
// Sum: 30
Rendimiento
Con relación a las asignaciones simples, las conversiones boxing y unboxing son
procesos que consumen muchos recursos. Cuando se aplica la conversión boxing a un
tipo de valor, se debe asignar y construir un objeto completamente nuevo. En menor
grado, la conversión de tipos requerida para aplicar la conversión unboxing también es
costosa. Para más información, vea Rendimiento.
Boxing
La conversión boxing se utiliza para almacenar tipos de valor en el montón de
recolección de elementos no utilizados. La conversión boxing es una conversión
implícita de un tipo de valor en el tipo object o en cualquier tipo de interfaz
implementado por este tipo de valor. Al aplicar la conversión boxing a un tipo de valor
se asigna una instancia de objeto en el montón y se copia el valor en el nuevo objeto.
C#
int i = 123;
C#
object o = i;
El resultado de esta instrucción es crear una referencia de objeto o en la pila que hace
referencia a un valor del tipo int en el montón. Este valor es una copia del tipo de valor
asignado a la variable i . La diferencia entre las dos variables, i y o , se muestra en la
imagen siguiente de la conversión boxing:
C#
int i = 123;
Ejemplo
Este ejemplo convierte una variable de entero i en un objeto o mediante la conversión
boxing. A continuación, el valor almacenado en la variable i se cambia de 123 a 456 . El
ejemplo muestra que el tipo de valor original y el objeto al que se ha aplicado la
conversión boxing usan ubicaciones de memoria independientes y, por consiguiente,
pueden almacenar valores diferentes.
C#
class TestBoxing
int i = 123;
object o = i;
/* Output:
*/
Unboxing
La conversión unboxing es una conversión explícita del tipo object en un tipo de valor
o de un tipo de interfaz en un tipo de valor que implementa la interfaz. Una operación
de conversión unboxing consiste en lo siguiente:
C#
object o = i; // boxing
Para que la conversión unboxing de tipos de valor sea correcta en tiempo de ejecución,
el elemento al que se aplica debe ser una referencia a un objeto creado previamente
mediante la conversión boxing de una instancia de ese tipo de valor. Si se intenta aplicar
la conversión unboxing a null , se producirá una excepción NullReferenceException. Si
se intenta aplicar la conversión unboxing a una referencia de un tipo de valor
incompatible, se producirá una excepción InvalidCastException.
Ejemplo
El ejemplo siguiente muestra un caso de conversión unboxing no válida y la excepción
InvalidCastException resultante. Si se utiliza try y catch , se muestra un mensaje de
error cuando se produce el error.
C#
class TestUnboxing
int i = 123;
try
System.Console.WriteLine("Unboxing OK.");
catch (System.InvalidCastException e)
Si cambia la instrucción:
C#
int j = (short)o;
a:
C#
int j = (int)o;
Unboxing OK.
Especificación del lenguaje C#
Para obtener más información, consulte la Especificación del lenguaje C#. La
especificación del lenguaje es la fuente definitiva de la sintaxis y el uso de C#.
Vea también
Guía de programación de C#
Tipos de referencia
Tipos de valor
Procedimiento Convertir una matriz de
bytes en un valor int (Guía de
programación de C#)
Artículo • 04/01/2023 • Tiempo de lectura: 2 minutos
En este ejemplo se muestra cómo usar la clase BitConverter para convertir una matriz de
bytes en un valor int y de nuevo en una matriz de bytes. Por ejemplo, es posible que
tenga que realizar una conversión de bytes a un tipo de datos integrado después de
leer los bytes fuera de la red. Además del método ToInt32(Byte[], Int32) del ejemplo, en
la tabla siguiente se muestran los métodos de la clase BitConverter que sirven para
convertir bytes (de una matriz de bytes) en otros tipos integrados.
Ejemplos
En este ejemplo se inicializa una matriz de bytes, se invierte la matriz si la arquitectura
de equipo es little-endian (es decir, en primer lugar se almacena el byte menos
significativo) y, después, se llama al método ToInt32(Byte[], Int32) para convertir cuatro
bytes de la matriz en int . El segundo argumento de ToInt32(Byte[], Int32)especifica el
índice de inicio de la matriz de bytes.
7 Nota
C#
byte[] bytes = { 0, 0, 0, 25 };
if (BitConverter.IsLittleEndian)
Array.Reverse(bytes);
7 Nota
C#
Consulte también
BitConverter
IsLittleEndian
Tipos
Procedimiento Convertir una cadena en
un número (Guía de programación de
C#)
Artículo • 15/02/2023 • Tiempo de lectura: 5 minutos
Resulta algo más eficaz y sencillo llamar a un método TryParse (por ejemplo,
int.TryParse("11", out number)) o un método Parse (por ejemplo, var number =
int.Parse("11")). El uso de un método Convert resulta más práctico para objetos
generales que implementan IConvertible.
Puede usar los métodos Parse o TryParse sobre el tipo numérico que espera que
contenga la cadena, como el tipo System.Int32. El método Convert.ToInt32 utiliza Parse
internamente. El método Parse devuelve el número convertido; el método TryParse
devuelve un valor booleano que indica si la conversión se realizó correctamente, y
devuelve el número convertido en un parámetro out . Si el formato de la cadena no es
válido, Parse inicia una excepción, pero TryParse devuelve false . Cuando se llama a un
método Parse , siempre debe usar control de excepciones para detectar un
FormatException cuando se produzca un error en la operación de análisis.
using System;
try
Console.WriteLine(result);
catch (FormatException)
try
Console.WriteLine(numVal);
catch (FormatException e)
{
Console.WriteLine(e.Message);
// Output: -105
Console.WriteLine(j);
}
else
// Output: -105
try
int m = Int32.Parse("abc");
catch (FormatException e)
{
Console.WriteLine(e.Message);
Console.WriteLine(numValue);
else
En el ejemplo siguiente se muestra un enfoque para analizar una cadena que se espera
que incluya caracteres numéricos iniciales (incluidos caracteres hexadecimales) y
caracteres no numéricos finales. Asigna caracteres válidos desde el principio de una
cadena a una nueva cadena antes de llamar al método TryParse. Dado que las cadenas
que va a analizar contiene pocos caracteres, el ejemplo llama al método String.Concat
para asignar caracteres válidos a una nueva cadena. Para una cadena más larga, se
puede usar la clase StringBuilder en su lugar.
C#
using System;
else
break;
if (int.TryParse(numericString,
System.Globalization.NumberStyles.HexNumber, null, out int i))
numericString = "";
else
break;
decimal ToDecimal(String)
float ToSingle(String)
double ToDouble(String)
short ToInt16(String)
int ToInt32(String)
long ToInt64(String)
ushort ToUInt16(String)
uint ToUInt32(String)
ulong ToUInt64(String)
En este ejemplo, se llama al método Convert.ToInt32(String) para convertir una cadena
de entrada en un valor int. El ejemplo detecta las dos excepciones más comunes que
este método puede generar, FormatException y OverflowException. Si se puede
incrementar el número resultante sin exceder Int32.MaxValue, el ejemplo suma 1 al
resultado y muestra la salida.
C#
using System;
while (repeat)
try
numVal = Convert.ToInt32(input);
else
catch (FormatException)
catch (OverflowException)
string go = Console.ReadLine();
if (go.ToUpper() != "Y")
repeat = false;
// Sample Output:
// Go again? Y/N: y
// Go again? Y/N: y
// Go again? Y/N: n
Ejemplos
En este ejemplo se genera el valor hexadecimal de cada uno de los caracteres de
string . Primero, analiza string como una matriz de caracteres. Después, llama a
C#
/* Output:
Hexadecimal value of H is 48
Hexadecimal value of e is 65
Hexadecimal value of l is 6C
Hexadecimal value of l is 6C
Hexadecimal value of o is 6F
Hexadecimal value of is 20
Hexadecimal value of W is 57
Hexadecimal value of o is 6F
Hexadecimal value of r is 72
Hexadecimal value of l is 6C
Hexadecimal value of d is 64
Hexadecimal value of ! is 21
*/
C#
/* Output:
*/
C#
Console.WriteLine(num);
//Output: 2274
C#
// Output: 200.0056
En el ejemplo siguiente se muestra cómo convertir una matriz byte en una cadena
hexadecimal mediante la clase System.BitConverter.
C#
Console.WriteLine(str);
Console.WriteLine(str);
/*Output:
01-AA-B1-DC-10-DD
01AAB1DC10DD
*/
En el ejemplo siguiente se muestra cómo convertir una matriz byte en una cadena
hexadecimal mediante una llamada al método Convert.ToHexString introducido en
.NET 5.0.
C#
Console.WriteLine(hexValue);
/*Output:
646F74636574
*/
Vea también
Cadenas con formato numérico estándar
Tipos
Determinación de si una cadena representa un valor numérico
Using type dynamic
Artículo • 25/02/2023 • Tiempo de lectura: 4 minutos
The dynamic type is a static type, but an object of type dynamic bypasses static type
checking. In most cases, it functions like it has type object . The compiler assumes a
dynamic element supports any operation. Therefore, you don't have to determine
whether the object gets its value from a COM API, from a dynamic language such as
IronPython, from the HTML Document Object Model (DOM), from reflection, or from
somewhere else in the program. However, if the code isn't valid, errors surface at run
time.
For example, if instance method exampleMethod1 in the following code has only one
parameter, the compiler recognizes that the first call to the method,
ec.exampleMethod1(10, 4) , isn't valid because it contains two arguments. The call causes
a compiler error. The compiler doesn't check the second call to the method,
dynamic_ec.exampleMethod1(10, 4) , because the type of dynamic_ec is dynamic .
Therefore, no compiler error is reported. However, the error doesn't escape notice
indefinitely. It appears at run time and causes a run-time exception.
C#
//ec.exampleMethod1(10, 4);
dynamic_ec.exampleMethod1(10, 4);
dynamic_ec.nonexistentMethod();
C#
class ExampleClass
public ExampleClass() { }
public ExampleClass(int v) { }
The role of the compiler in these examples is to package together information about
what each statement is proposing to do to the dynamic object or expression. The
runtime examines the stored information and any statement that isn't valid causes a
run-time exception.
The result of most dynamic operations is itself dynamic . For example, if you rest the
mouse pointer over the use of testSum in the following example, IntelliSense displays
the type (local variable) dynamic testSum.
C#
dynamic d = 1;
var testSum = d + 3;
System.Console.WriteLine(testSum);
For example, the type of testInstance in the following declaration is ExampleClass , not
dynamic :
C#
Conversions
Conversions between dynamic objects and other types are easy. Conversions enable the
developer to switch between dynamic and non-dynamic behavior.
You can convert any to dynamic implicitly, as shown in the following examples.
C#
dynamic d1 = 7;
dynamic d3 = System.DateTime.Today;
dynamic d4 = System.Diagnostics.Process.GetProcesses();
Conversely, you can dynamically apply any implicit conversion to any expression of type
dynamic .
C#
int i = d1;
DateTime dt = d3;
C#
// Valid.
ec.exampleMethod2("a string");
// The following statement does not cause a compiler error, even though ec
is not
ec.exampleMethod2(d1);
//ec.exampleMethod2(7);
COM interop
Many COM methods allow for variation in argument types and return type by
designating the types as object . COM interop necessitates explicit casting of the values
to coordinate with strongly typed variables in C#. If you compile by using the
EmbedInteropTypes (C# Compiler Options) option, the introduction of the dynamic type
enables you to treat the occurrences of object in COM signatures as if they were of
type dynamic , and thereby to avoid much of the casting. For more information on using
the dynamic type with COM objects, see the article on How to access Office interop
objects by using C# features.
Related articles
Title Description
Dynamic Language Provides an overview of the DLR, which is a runtime environment that
Runtime Overview adds a set of services for dynamic languages to the common language
runtime (CLR).
Walkthrough: Creating Provides step-by-step instructions for creating a custom dynamic object
and Using Dynamic and for creating a project that accesses an IronPython library.
Objects
Tutorial: Crear y usar objetos dinámicos
en C#
Artículo • 03/03/2023 • Tiempo de lectura: 9 minutos
Puede crear objetos dinámicos personalizados con las clases del espacio de nombres
System.Dynamic. Por ejemplo, puede crear un objeto ExpandoObject y especificar los
miembros de ese objeto en tiempo de ejecución. También puede crear su propio tipo
que hereda la clase DynamicObject. Después, puede invalidar los miembros de la clase
DynamicObject para proporcionar funciones dinámicas en tiempo de ejecución.
Requisitos previos
Visual Studio 2022, versión 17.3 o posterior con la carga de trabajo Desarrollo de
escritorio de .NET instalada. El SDK de .NET 7 se incluye al seleccionar esta carga
de trabajo.
7 Nota
C#
using System.IO;
using System.Dynamic;
El objeto dinámico personalizado usa una enumeración para determinar los criterios de
búsqueda. Antes de la instrucción de clase, agregue la siguiente definición de
enumeración.
C#
StartsWith,
Contains,
EndsWith
C#
Agregue el código siguiente a la clase ReadOnlyFile para definir un campo privado para
la ruta de acceso y un constructor para la clase ReadOnlyFile .
C#
// Store the path to the file and the initial line count value.
// Public constructor. Verify that file exists and store the path in
if (!File.Exists(filePath))
p_filePath = filePath;
C#
StringSearchOption StringSearchOption =
StringSearchOption.StartsWith,
StreamReader sr = null;
try
sr = new StreamReader(p_filePath);
while (!sr.EndOfStream)
line = sr.ReadLine();
testLine = line.ToUpper();
switch (StringSearchOption)
case StringSearchOption.StartsWith:
if (testLine.StartsWith(propertyName.ToUpper())) {
results.Add(line); }
break;
case StringSearchOption.Contains:
if (testLine.Contains(propertyName.ToUpper())) {
results.Add(line); }
break;
case StringSearchOption.EndsWith:
if (testLine.EndsWith(propertyName.ToUpper())) {
results.Add(line); }
break;
catch
// Trap any exception that occurs in reading the file and return
null.
results = null;
finally
return results;
C#
result = GetPropertyValue(binder.Name);
C#
object[] args,
try
catch
try
catch
text
ejecución para llamar a miembros dinámicos y recuperar líneas de texto que contienen
la cadena "Customer".
C#
Console.WriteLine(line);
Console.WriteLine("----------------------------");
Console.WriteLine(line);
C#
using System.Linq;
using Microsoft.Scripting.Hosting;
using IronPython.Hosting;
C#
System.IO.Directory.SetCurrentDirectory(
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) +
@"\IronPython 2.7\Lib");
Console.WriteLine("Loading random.py");
ScriptRuntime py = Python.CreateRuntime();
Console.WriteLine("random.py loaded.");
Una vez que el código haya cargado el módulo random.py, agregue el código siguiente
para crear una matriz de enteros. La matriz se pasa al método shuffle del módulo
random.py, que ordena aleatoriamente los valores de la matriz.
C#
random.shuffle(items);
Console.WriteLine(item);
Console.WriteLine("-------------------");
Consulte también
System.Dynamic
System.Dynamic.DynamicObject
Uso de tipo dinámico
dynamic
Implementar interfaces dinámicas (PDF descargable de Microsoft TechNet)
Control de versiones con las palabras
clave Override y New (Guía de
programación de C#)
Artículo • 02/03/2023 • Tiempo de lectura: 5 minutos
El lenguaje C# está diseñado para que las versiones entre clases base y derivadas de
diferentes bibliotecas puedan evolucionar y mantener la compatibilidad con versiones
anteriores. Esto significa, por ejemplo, que la introducción de un nuevo miembro en una
clase base con el mismo nombre que un miembro de una clase derivada es totalmente
compatible con C# y no lleva a un comportamiento inesperado. Además, implica que
una clase debe declarar explícitamente si un método está pensado para reemplazar un
método heredado o si se trata de un nuevo método que oculta un método heredado de
nombre similar.
En C#, las clases derivadas pueden contener métodos con el mismo nombre que los
métodos de clase base.
C#
class GraphicsClass
Su compañía usa esta clase y usted la usa para derivar su propia clase, agregando un
nuevo método:
C#
La aplicación se usa sin problemas, hasta que la compañía A lanza una nueva versión de
GraphicsClass , que es similar al código siguiente:
C#
class GraphicsClass
Si quiere que su método reemplace al nuevo método de clase base, use la palabra clave
override :
C#
C#
base.DrawRectangle();
Si no quiere que el método reemplace al nuevo método de clase base, se aplican las
consideraciones siguientes. Para evitar la confusión entre los dos métodos, puede
cambiarle el nombre a su método. Esto puede ser un proceso lento y propenso a errores
y no resultar práctico en algunos casos. Pero si el proyecto es relativamente pequeño,
puede usar opciones de refactorización de Visual Studio para cambiar el nombre del
método. Para obtener más información, vea Refactoring Classes and Types (Class
Designer) (Refactorización de clases y tipos [Diseñador de clases]).
C#
Con la palabra clave new se indica al compilador que su definición oculta la definición
contenida en la clase base. Éste es el comportamiento predeterminado.
C#
C#
int val = 5;
C#
((Base)d).DoWork(val); // Calls DoWork(int) on Derived.
Para obtener otros ejemplos de new y override , vea Saber cuándo utilizar las palabras
clave Override y New (Guía de programación de C#).
Vea también
Guía de programación de C#
El sistema de tipos de C#
Métodos
Herencia
Saber cuándo utilizar las palabras clave
Override y New (Guía de programación
de C#)
Artículo • 10/02/2023 • Tiempo de lectura: 11 minutos
En C#, un método de una clase derivada puede tener el mismo nombre que un método
de la clase base. Se puede especificar cómo interactúan los métodos mediante las
palabras clave new y override. El modificador override extiende el método de clase base
virtual y el modificador new oculta un método de clase base accesible. En los ejemplos
de este tema se ilustra la diferencia.
C#
class BaseClass
Console.WriteLine("Base - Method1");
Console.WriteLine("Derived - Method2");
Dado que bc y bcdc tienen el tipo BaseClass , solo pueden tener acceso directo a
Method1 , a menos que se use la conversión. La variable dc puede tener acceso a
Method1 y Method2 . Estas relaciones se muestran en el código siguiente.
C#
class Program
bc.Method1();
dc.Method1();
dc.Method2();
bcdc.Method1();
// Output:
// Base - Method1
// Base - Method1
// Derived - Method2
// Base - Method1
C#
Console.WriteLine("Base - Method2");
Dado que BaseClass ahora tiene un método Method2 , se puede agregar una segunda
instrucción de llamada para las variables de BaseClass bc y bcdc , como se muestra en el
código siguiente.
C#
bc.Method1();
bc.Method2();
dc.Method1();
dc.Method2();
bcdc.Method1();
bcdc.Method2();
Al compilar el proyecto, verá que la adición del método Method2 de BaseClass genera
una advertencia. La advertencia indica que el método Method2 de DerivedClass oculta el
método Method2 de BaseClass . Se recomienda usar la palabra clave new en la definición
de Method2 si se pretende provocar ese resultado. Como alternativa, se puede cambiar
el nombre de uno de los métodos Method2 para resolver la advertencia, pero eso no
siempre resulta práctico.
Antes de agregar new , ejecute el programa para ver el resultado producido por las
instrucciones adicionales que realizan la llamada. Se muestran los resultados siguientes.
C#
// Output:
// Base - Method1
// Base - Method2
// Base - Method1
// Derived - Method2
// Base - Method1
// Base - Method2
La palabra clave new conserva las relaciones que generan ese resultado, pero se suprime
la advertencia. Las variables de tipo BaseClass siguen teniendo acceso a los miembros
de BaseClass y la variable de tipo DerivedClass sigue teniendo acceso a los miembros
de DerivedClass en primer lugar y, después, tiene en cuenta los miembros heredados
de BaseClass .
C#
Console.WriteLine("Derived - Method2");
C#
Console.WriteLine("Derived - Method1");
C#
Console.WriteLine("Base - Method1");
Vuelva a ejecutar el proyecto. Observe especialmente las dos últimas líneas del
resultado siguiente.
C#
// Output:
// Base - Method1
// Base - Method2
// Derived - Method1
// Derived - Method2
// Derived - Method1
// Base - Method2
El uso del modificador override permite que bcdc tenga acceso al método Method1 que
se define en DerivedClass . Normalmente, es el comportamiento deseado en jerarquías
de herencia. La intención es que los objetos que tienen valores que se crean a partir de
la clase derivada usen los métodos que se definen en la clase derivada. Ese
comportamiento se consigue mediante el uso de override para extender el método de
clase base.
C#
using System;
using System.Text;
namespace OverrideAndNew
class Program
// The following two calls do what you would expect. They call
bc.Method1();
bc.Method2();
// Output:
// Base - Method1
// Base - Method2
// The following two calls do what you would expect. They call
dc.Method1();
dc.Method2();
// Output:
// Derived - Method1
// Derived - Method2
bcdc.Method1();
bcdc.Method2();
// Output:
// Derived - Method1
// Base - Method2
class BaseClass
Console.WriteLine("Base - Method1");
Console.WriteLine("Base - Method2");
Console.WriteLine("Derived - Method1");
Console.WriteLine("Derived - Method2");
C#
// Define the base class, Car. The class defines two methods,
// class also defines a ShowDetails method. The example tests which version
of
class Car
ShowDetails();
System.Console.WriteLine("Standard transportation.");
instancia.
C#
System.Console.WriteLine("\nTestCars1");
System.Console.WriteLine("----------");
car1.DescribeCar();
System.Console.WriteLine("----------");
// Notice the output from this test case. The new modifier is
// class.
car2.DescribeCar();
System.Console.WriteLine("----------");
car3.DescribeCar();
System.Console.WriteLine("----------");
C#
// TestCars1
// ----------
// Standard transportation.
// ----------
// Standard transportation.
// ----------
// ----------
TestCars2 crea una lista de objetos que tienen el tipo Car . Se crean instancias de los
valores de los objetos desde las clases Car , ConvertibleCar y Minivan . DescribeCar se
llama en cada elemento de la lista. En el código siguiente se muestra la definición de
TestCars2 .
C#
System.Console.WriteLine("\nTestCars2");
System.Console.WriteLine("----------");
new Minivan() };
car.DescribeCar();
System.Console.WriteLine("----------");
C#
// TestCars2
// ----------
// Standard transportation.
// ----------
// Standard transportation.
// ----------
// ----------
C#
System.Console.WriteLine("\nTestCars3");
System.Console.WriteLine("----------");
car2.ShowDetails();
car3.ShowDetails();
System.Console.WriteLine("\nTestCars4");
System.Console.WriteLine("----------");
car2.ShowDetails();
car3.ShowDetails();
Los métodos generan el siguiente resultado, que se corresponde a los resultados del
primer ejemplo de este tema.
C#
// TestCars3
// ----------
// TestCars4
// ----------
// Standard transportation.
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace OverrideAndNew2
class Program
TestCars1();
TestCars2();
// directly.
TestCars3();
TestCars4();
System.Console.WriteLine("\nTestCars1");
System.Console.WriteLine("----------");
car1.DescribeCar();
System.Console.WriteLine("----------");
// Notice the output from this test case. The new modifier is
// class.
car2.DescribeCar();
System.Console.WriteLine("----------");
car3.DescribeCar();
System.Console.WriteLine("----------");
// Output:
// TestCars1
// ----------
// Standard transportation.
// ----------
// Standard transportation.
// ----------
// ----------
System.Console.WriteLine("\nTestCars2");
System.Console.WriteLine("----------");
new Minivan() };
car.DescribeCar();
System.Console.WriteLine("----------");
// Output:
// TestCars2
// ----------
// Standard transportation.
// ----------
// Standard transportation.
// ----------
// ----------
System.Console.WriteLine("\nTestCars3");
System.Console.WriteLine("----------");
car2.ShowDetails();
car3.ShowDetails();
}
// Output:
// TestCars3
// ----------
System.Console.WriteLine("\nTestCars4");
System.Console.WriteLine("----------");
car2.ShowDetails();
car3.ShowDetails();
}
// Output:
// TestCars4
// ----------
// Standard transportation.
// Define the base class, Car. The class defines two virtual methods,
class Car
ShowDetails();
System.Console.WriteLine("Standard transportation.");
Vea también
Guía de programación de C#
El sistema de tipos de C#
Control de versiones con las palabras clave Override y New
base
abstract
Procedimiento para invalidar el método
ToString (Guía de programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Cada clase o struct de C# hereda implícitamente la clase Object. Por consiguiente, cada
objeto de C# obtiene el método ToString, que devuelve una representación de cadena
de ese objeto. Por ejemplo, todas las variables de tipo int tienen un método ToString ,
que las habilita para devolver su contenido como cadena:
C#
int x = 42;
Console.WriteLine(strx);
// Output:
// 42
Cuando cree una clase o struct personalizados, debe reemplazar el método ToString
para proporcionar información sobre el tipo al código de cliente.
Para obtener información sobre cómo usar cadenas de formato y otros tipos de formato
personalizado con el método ToString , vea Aplicar formato a tipos.
) Importante
C#
C#
class Person
C#
Console.WriteLine(person);
// Output:
// Person: John 12
Vea también
IFormattable
Guía de programación de C#
El sistema de tipos de C#
Cadenas
string
override
virtual
Aplicación de formato a tipos
Miembros (Guía de programación de
C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Las clases y structs tienen miembros que representan sus datos y comportamiento. Los
miembros de una clase incluyen todos los miembros declarados en la clase, junto con
todos los miembros (excepto constructores y finalizadores) declarados en todas las
clases de su jerarquía de herencia. Los miembros privados de clases base se heredan en
las clases derivadas, pero estas no pueden tener acceso a ellos.
En la tabla siguiente se enumeran los tipos de miembros que puede contener una clase
o struct:
Miembro Descripción
Campos Los campos son variables declaradas en el ámbito de clase. Un campo puede ser
un tipo numérico integrado o una instancia de otra clase. Por ejemplo, una clase
de calendario puede tener un campo con la fecha actual.
Constantes Las constantes son campos cuyo valor se establece en tiempo de compilación y
no se puede cambiar.
Propiedades Las propiedades son métodos de una clase a los que se obtiene acceso como si
fueran campos de esa clase. Una propiedad puede proporcionar protección a un
campo de clase con el fin de evitar que se cambie sin el conocimiento del objeto.
Métodos Los métodos definen las acciones que una clase puede realizar. Los métodos
pueden aceptar parámetros que proporcionan datos de entrada y devolver datos
de salida a través de parámetros. Los métodos también pueden devolver un valor
directamente, sin usar ningún parámetro.
Eventos Los eventos proporcionan a otros objetos notificaciones sobre lo que ocurre,
como clics en botones o la realización correcta de un método. Los eventos se
definen y desencadenan mediante delegados.
Indizadores Los indizadores permiten indizar un objeto de manera similar a como se hace con
las matrices.
Constructores Los constructores son métodos a los que se llama cuando el objeto se crea por
primera vez. Se usan a menudo para inicializar los datos de un objeto.
Miembro Descripción
Finalizadores En C#, los finalizadores se usan en raras ocasiones. Son métodos a los que llama
el motor de ejecución del runtime cuando el objeto está a punto de quitarse de
la memoria. Generalmente se utilizan para asegurarse de que los recursos que se
deben liberar se controlan apropiadamente.
Tipos Los tipos anidados son tipos declarados dentro de otro tipo. Los tipos anidados
anidados se usan a menudo para describir objetos utilizados únicamente por los tipos que
los contienen.
Vea también
Guía de programación de C#
Clases
Clases y miembros de clase abstractos y
sellados (Guía de programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
La palabra clave abstract permite crear clases y miembros class que están incompletos y
se deben implementar en una clase derivada.
La palabra clave sealed permite impedir la herencia de una clase o de ciertos miembros
de clase marcados previamente como virtual.
C#
Las clases abstractas también pueden definir métodos abstractos. Esto se consigue
agregando la palabra clave abstract antes del tipo de valor que devuelve el método.
Por ejemplo:
C#
C#
public class D
// Original implementation.
public class F : E
// New implementation.
Si un método virtual se declara como abstract , sigue siendo virtual para cualquier
clase que herede de la clase abstracta. Una clase que hereda un método abstracto no
puede tener acceso a la implementación original del método: en el ejemplo anterior,
DoWork en la clase F no puede llamar a DoWork en la clase D. De esta manera, una clase
C#
public sealed class D
Una clase sellada no se puede utilizar como clase base. Por esta razón, tampoco puede
ser una clase abstracta. Las clases selladas evitan la derivación. Puesto que nunca se
pueden utilizar como clase base, algunas optimizaciones en tiempo de ejecución
pueden hacer que sea un poco más rápido llamar a miembros de clase sellada.
C#
public class D : C
Consulte también
Guía de programación de C#
El sistema de tipos de C#
Herencia
Métodos
Campos
Procedimiento para definir propiedades abstractas
Clases estáticas y sus miembros (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 6 minutos
Una clase estática es básicamente lo mismo que una clase no estática, con la diferencia
de que no se pueden crear instancias de una clase estática. En otras palabras, no puede
usar el operador new para crear una variable del tipo de clase. Dado que no hay
ninguna variable de instancia, para tener acceso a los miembros de una clase estática,
debe usar el nombre de la clase. Por ejemplo, si tiene una clase estática denominada
UtilityClass que tiene un método estático público denominado MethodA , llame al
método tal como se muestra en el ejemplo siguiente:
C#
UtilityClass.MethodA();
Es posible usar una clase estática como un contenedor adecuado para conjuntos de
métodos que solo funcionan en parámetros de entrada y que no tienen que obtener ni
establecer campos de instancias internas. Por ejemplo, en la biblioteca de clases .NET, la
clase estática System.Math contiene métodos que realizan operaciones matemáticas, sin
ningún requisito para almacenar o recuperar datos que sean únicos de una instancia
concreta de la clase Math. Es decir, para aplicar los miembros de la clase, debe
especificar el nombre de clase y el nombre de método, como se muestra en el ejemplo
siguiente.
C#
Console.WriteLine(Math.Abs(dub));
Console.WriteLine(Math.Floor(dub));
Console.WriteLine(Math.Round(Math.Abs(dub)));
// Output:
// 3.14
// -4
// 3
Como sucede con todos los tipos de clase, el entorno de ejecución de .NET carga la
información de tipo para una clase estática cuando se carga el programa que hace
referencia a la clase. El programa no puede especificar exactamente cuándo se carga la
clase, pero existe la garantía de que se cargará, de que sus campos se inicializarán y de
que se llamará a su constructor estático antes de que se haga referencia a la clase por
primera vez en el programa. Solo se llama una vez a un constructor estático, y una clase
estática permanece en memoria durante la vigencia del dominio de aplicación en el que
reside el programa.
7 Nota
Para crear una clase no estática que solo permita la creación de una instancia de sí
misma, vea Implementing Singleton in C# (Implementar un singleton en C#).
Está sellada.
Por lo tanto, crear una clase estática es básicamente lo mismo que crear una clase que
contiene solo miembros estáticos y un constructor privado. Un constructor privado
impide que se creen instancias de la clase. La ventaja de usar una clase estática es que el
compilador puede comprobar que no se agregue accidentalmente ningún miembro de
instancia. El compilador garantizará que no se creen instancias de esta clase.
Las clases estáticas están selladas y, por lo tanto, no pueden heredarse. No pueden
heredar de ninguna clase excepto Object. Las clases estáticas no pueden contener un
constructor de instancia, aunque sí un constructor estático. Las clases no estáticas
también deben definir un constructor estático si la clase contiene miembros estáticos
que requieren inicialización no trivial. Para obtener más información, vea Constructores
estáticos.
Ejemplo
A continuación se muestra un ejemplo de una clase estática que contiene dos métodos
que convierten la temperatura de grados Celsius a grados Fahrenheit y viceversa:
C#
return fahrenheit;
return celsius;
class TestTemperatureConverter
Console.Write(":");
double F, C = 0;
switch (selection)
case "1":
F =
TemperatureConverter.CelsiusToFahrenheit(Console.ReadLine() ?? "0");
break;
case "2":
C =
TemperatureConverter.FahrenheitToCelsius(Console.ReadLine() ?? "0");
break;
default:
break;
Console.ReadKey();
/* Example Output:
:2
*/
Miembros estáticos
Una clase no estática puede contener métodos, campos, propiedades o eventos
estáticos. El miembro estático es invocable en una clase, incluso si no se ha creado
ninguna instancia de la clase. Siempre se tiene acceso al miembro estático con el
nombre de clase, no con el nombre de instancia. Solo existe una copia de un miembro
estático, independientemente del número de instancias de la clase que se creen. Los
métodos y las propiedades estáticos no pueden acceder a campos y eventos no
estáticos en su tipo contenedor, ni tampoco a una variable de instancia de un objeto a
menos que se pase explícitamente en un parámetro de método.
Es más habitual declarar una clase no estática con algunos miembros estáticos que
declarar toda una clase como estática. Dos usos habituales de los campos estáticos son
llevar la cuenta del número de objetos de los que se han creado instancias y almacenar
un valor que se debe compartir entre todas las instancias.
instancia de objeto.
C# no admite variables locales estáticas (es decir, variables que se declaran en el ámbito
del método).
Para declarar miembros de clases estáticas, use la palabra clave static antes del tipo de
valor devuelto del miembro, como se muestra en el ejemplo siguiente:
C#
get
return 15;
Los miembros estáticos se inicializan antes de que se obtenga acceso por primera vez al
miembro estático y antes de que se llame al constructor estático, en caso de haberlo.
Para tener acceso a un miembro de clase estática, use el nombre de la clase en lugar de
un nombre de variable para especificar la ubicación del miembro, como se muestra en el
ejemplo siguiente:
C#
Automobile.Drive();
int i = Automobile.NumberOfWheels;
public: Puede obtener acceso al tipo o miembro cualquier otro código del mismo
ensamblado o de otro ensamblado que haga referencia a éste. El nivel de
accesibilidad de los miembros públicos de un tipo se controla mediante el nivel de
accesibilidad del propio tipo.
private: solamente el código de la misma class o struct puede acceder al tipo o
miembro.
protected: solamente el código de la misma class , o bien de una class derivada
de esa class , puede acceder al tipo o miembro.
internal: Puede obtener acceso al tipo o miembro cualquier código del mismo
ensamblado, pero no de un ensamblado distinto. En otras palabras, se puede
acceder a tipos o miembros internal desde el código que forma parte de la
misma compilación.
protected internal: cualquier código del ensamblado en el que se ha declarado, o
desde una class derivada de otro ensamblado, puede acceder al tipo o miembro.
private protected: se puede tener acceso al tipo o miembro mediante tipos
derivados del objeto class que se declaran dentro de su ensamblado contenedor.
Tabla de resumen
Ubicación del autor de public protected protected internal private private
la llamada internal protected
Clase no derivada ✔️ ✔️ ❌ ✔️ ❌ ❌
(mismo ensamblado)
Ubicación del autor de public protected protected internal private private
la llamada internal protected
C#
No todos los modificadores de acceso son válidos para todos los tipos o miembros de
todos los contextos. En algunos casos, la accesibilidad de un miembro de tipo está
restringida por la accesibilidad de su tipo contenedor.
predeterminado es internal .
Los miembros de estructura, incluidas las clases y las estructuras anidadas, se pueden
declarar como public , internal o private . Los miembros de clase, incluidas las clases y
las estructuras anidadas, pueden ser public , protected internal , protected , internal ,
private protected o private . Los miembros de clase y estructura, incluidas las clases y
las estructuras anidadas, tienen acceso private de forma predeterminada. Los tipos
anidados privados no son accesibles desde fuera del tipo contenedor.
Las clases derivadas y los registros derivados no pueden tener mayor accesibilidad que
sus tipos base. No se puede declarar una clase pública B que derive de una clase
interna A . Si se permitiera, convertiría A en público, porque todos los miembros
protected o internal de A son accesibles desde la clase derivada.
Puede habilitar otros ensamblados concretos para acceder a los tipos internos mediante
InternalsVisibleToAttribute . Para más información, vea Ensamblados de confianza.
El tipo de cualquier miembro que sea un campo, propiedad o evento debe ser al menos
tan accesible como el propio miembro. Del mismo modo, el tipo devuelto y los tipos de
parámetro de cualquier método, indizador o delegado deben ser al menos tan
accesibles como el propio miembro. Por ejemplo, no puede tener un método public M
que devuelva una clase C a menos que C también sea public . Del mismo modo, no
puede tener una propiedad protected de tipo A si A se declara como private .
Los operadores definidos por el usuario siempre se deben declarar como public y
static . Para obtener más información, vea Sobrecarga de operadores.
C#
// public class:
// protected method:
// private field:
Otros tipos
Las interfaces declaradas directamente en un espacio de nombres pueden ser public o
internal y, al igual que las clases y las estructuras, su valor predeterminado es el acceso
Los miembros de enumeración siempre son public y no se les puede aplicar ningún
modificador de acceso.
Vea también
Especificación del orden del modificador (regla de estilo IDE0036)
Guía de programación de C#
El sistema de tipos de C#
Interfaces
private
public
internal
protected
protected internal
private protected
class
struct
interface
Campos (Guía de programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 4 minutos
Un campo es una variable de cualquier tipo que se declara directamente en una clase o
struct. Los campos son miembros de su tipo contenedor.
Una clase o struct puede tener campos de instancia, campos estáticos o ambos. Los
campos de instancia son específicos de una instancia de un tipo. Si tiene una clase T ,
con un campo de instancia F , puede crear dos objetos de tipo T y modificar el valor de
F en cada objeto sin afectar el valor del otro objeto. Por el contrario, un campo estático
pertenece al propio tipo y se comparte entre todas las instancias de ese tipo. Solo
puede acceder al campo estático mediante el nombre del tipo. Si obtiene acceso al
campo estático mediante un nombre de instancia, obtendrá el error en tiempo de
compilación CS0176.
Por lo general, solo se deben usar campos para las variables que tienen accesibilidad
privada o protegida. Los datos que el tipo expone al código de cliente se deben
proporcionar mediante métodos, propiedades e indizadores. Mediante estas
construcciones para el acceso indirecto a los campos internos, se puede proteger de los
valores de entrada no válidos. Un campo privado que almacena los datos expuestos por
una propiedad pública se denomina memoria auxiliar o campo de respaldo.
Los campos almacenan habitualmente los datos que deben ser accesibles para más de
un método de tipo y que deben almacenarse durante más tiempo de lo que dura un
único método. Por ejemplo, es posible que un tipo que representa una fecha de
calendario tenga tres campos enteros: uno para el mes, otro para el día y otro para el
año. Las variables que no se usan fuera del ámbito de un único método se deben
declarar como variables locales dentro del campo del método.
C#
get
return _date;
set
_date = value;
else
DateTime dt = Convert.ToDateTime(dateString);
_date = dt;
else
DateTime dt = Convert.ToDateTime(dateString);
else
Para acceder a un campo en una instancia, agregue un punto después del nombre de
instancia, seguido del nombre del campo, como en instancename._fieldName . Por
ejemplo:
C#
birthday.Day = "Saturday";
C#
//...
7 Nota
Se pueden marcar campos como público, privado, protegido, interno, protegido interno
o privado protegido. Estos modificadores de acceso definen cómo los usuarios del tipo
pueden acceder a los campos. Para obtener más información, consulte Modificadores de
acceso.
Consulte también
Guía de programación de C#
El sistema de tipos de C#
Utilizar constructores
Herencia
Modificadores de acceso
Clases y miembros de clase abstractos y sellados
Constantes (Guía de programación de
C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Las constantes son valores inmutables que se conocen en tiempo de compilación y que
no cambian durante la vida del programa. Las constantes se declaran con el modificador
const. Solo los tipos integrados de C# (excluido System.Object) pueden declararse como
const . Los tipos definidos por el usuario, incluidas las clases, las estructuras y las
matrices, no pueden ser const . Use el modificador readonly para crear una clase, una
estructura o una matriz que se inicialice una vez en tiempo de ejecución (por ejemplo,
en un constructor) y que posteriormente no se pueda cambiar.
El tipo enum permite definir constantes con nombre para los tipos integrados enteros
(por ejemplo, int , uint , long , etc.). Para más información, vea enum.
C#
class Calendar1
7 Nota
C#
class Calendar2
La expresión que se usa para inicializar una constante puede hacer referencia a otra
constante si no crea una referencia circular. Por ejemplo:
C#
class Calendar3
C#
Este ejemplo consta de tres archivos, cada uno de los cuales se compila individualmente
y se hace referencia a su ensamblado resultante mediante la siguiente compilación:
Ejemplos
Este archivo declara la clase Shape que contiene la propiedad Area del tipo double .
C#
public Shape(string s)
Id = s;
public string Id
get
return name;
set
name = value;
get;
C#
C#
: base(id)
this.side = side;
get
: base(id)
this.radius = radius;
get
: base(id)
this.width = width;
this.height = height;
get
C#
class TestClass
Shape[] shapes =
};
System.Console.WriteLine("Shapes Collection");
System.Console.WriteLine(s);
/* Output:
Shapes Collection
*/
Vea también
Guía de programación de C#
El sistema de tipos de C#
Clases y miembros de clase abstractos y sellados
Propiedades
Definición de constantes en C#
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
7 Nota
Para definir valores constantes de tipos enteros ( int , byte y así sucesivamente) use un
tipo enumerado. Para más información, vea enum.
Para definir constantes no enteras, un enfoque es agruparlas en una única clase estática
denominada Constants . Esto necesitará que todas las referencias a las constantes vayan
precedidas por el nombre de clase, como se muestra en el ejemplo siguiente.
Ejemplo
C#
class Program
Console.WriteLine(secsFromSun);
El uso del calificador de nombre de clase ayuda a garantizar que usted y otros usuarios
que usan la constante comprenden que es una constante y que no puede modificarse.
Consulte también
El sistema de tipos de C#
Propiedades (Guía de programación de
C#)
Artículo • 11/02/2023 • Tiempo de lectura: 5 minutos
get ). Las propiedades de solo escritura son poco frecuentes y se suelen usar para
restringir el acceso a datos confidenciales.
Las propiedades simples que no necesitan ningún código de descriptor de acceso
personalizado se pueden implementar como definiciones de cuerpos de expresión
o como propiedades implementadas automáticamente.
C#
set
Puede acceder a las propiedades para obtener y establecer el valor como se muestra en
el ejemplo siguiente:
C#
t.Hours = 24;
// Time in hours: 24
Las propiedades de solo lectura pueden implementar el descriptor de acceso get como
miembro con forma de expresión. En este caso, no se usan ni la palabra clave del
descriptor de acceso get ni la palabra clave return . En el ejemplo siguiente se
implementa la propiedad de solo lectura Name como miembro con forma de expresión.
C#
_firstName = first;
_lastName = last;
Tanto el descriptor de acceso get como set se pueden implementar como miembros
con forma de expresión. En este caso, las palabras clave get y set deben estar
presentes. En el ejemplo siguiente se muestra el uso de definiciones de cuerpos de
expresión para ambos descriptores de acceso. La palabra clave return no se usa con el
descriptor de acceso get .
C#
string _name;
decimal _cost;
_name = name;
_cost = cost;
Si una propiedad tiene los descriptores de acceso get y set (o get y init ), ambos se
deben implementar de forma automática. Una propiedad implementada
automáticamente se define mediante las palabras clave get y set sin proporcionar
ninguna implementación. El ejemplo siguiente repite el anterior, salvo que Name y Price
son propiedades implementadas automáticamente. En el ejemplo también se quita el
constructor parametrizado, por lo que los objetos SaleItem ahora se inicializan con una
llamada al constructor sin parámetros y un inicializador de objeto.
C#
{ get; set; }
{ get; set; }
C#
{ get; set; }
{ get; set; }
Para crear SaleItem , debe establecer las propiedades Name y Price mediante
inicializadores de objeto, como se muestra en el código siguiente:
C#
Secciones relacionadas
Utilizar propiedades
Propiedades de interfaz
Comparación entre propiedades e indizadores
Restringir la accesibilidad del descriptor de acceso
Propiedades autoimplementadas
Consulte también
Indizadores
get (Palabra clave)
set (palabra clave)
Utilizar propiedades (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 9 minutos
Las propiedades combinan aspectos de los campos y los métodos. Para el usuario de un
objeto, una propiedad que parece un campo, el acceso a la propiedad necesita la misma
sintaxis. Para el implementador de una clase, una propiedad es uno o dos bloques de
código que representa un descriptor de acceso get o un descriptor de acceso set. El
bloque de código del descriptor de acceso get se ejecuta cuando se lee la propiedad; el
bloque de código del descriptor de acceso set se ejecuta cuando se asigna un nuevo
valor a la propiedad. Una propiedad sin un descriptor de acceso set se considera de
solo lectura. Una propiedad sin un descriptor de acceso get se considera de solo
escritura. Una propiedad que tiene ambos descriptores de acceso es de lectura y
escritura. En C# 9 y versiones posteriores, puede usar un descriptor de acceso init en
lugar de set para que la propiedad sea de solo lectura.
A diferencia de los campos, las propiedades no se clasifican como variables. Por lo tanto,
no puede pasar una propiedad como un parámetro ref u out.
Las propiedades tienen muchos usos: pueden validar datos antes de permitir un cambio;
pueden exponer claramente datos en una clase donde esos datos se recuperan de otros
orígenes, como una base de datos; pueden realizar una acción cuando los datos se
cambian, como generar un evento, o cambiar el valor de otros campos.
C#
set
_month = value;
En este ejemplo, Month se declara como una propiedad, de manera que el descriptor de
acceso set pueda estar seguro de que el valor Month se establece entre 1 y 12. La
propiedad Month usa un campo privado para realizar un seguimiento del valor actual. La
ubicación real de los datos de una propiedad se conoce a menudo como la "memoria
auxiliar" de la propiedad. Es habitual que las propiedades usen campos privados como
memoria auxiliar. El campo se marca como privado para asegurarse de que solo puede
cambiarse llamando a la propiedad. Para obtener más información sobre las
restricciones de acceso público y privado, vea Modificadores de acceso.
C#
class Employee
//...
El descriptor de acceso get debe finalizar en una instrucción return o throw, y el control
no puede salir del cuerpo del descriptor de acceso.
2 Advertencia
El descriptor de acceso get puede usarse para devolver el valor de campo o para
calcularlo y devolverlo. Por ejemplo:
C#
class Manager
C#
class Student
C#
Observaciones
Las propiedades se pueden marcar como public , private , protected , internal ,
protected internal o private protected . Estos modificadores de acceso definen cómo
Una propiedad puede declararse como una propiedad estática mediante la palabra
clave static . Las propiedad estáticas están disponibles para los autores de la llamada
en cualquier momento, aunque no exista ninguna instancia de la clase. Para más
información, vea Clases estáticas y sus miembros.
Una propiedad puede marcarse como una propiedad virtual mediante la palabra clave
virtual. Las propiedades virtuales permiten que las clases derivadas invaliden el
comportamiento de la propiedad mediante la palabra clave override. Para obtener más
información sobre estas opciones, vea Herencia.
Una propiedad que invalida una propiedad virtual también puede sellarse, lo que
especifica que para las clases derivadas ya no es virtual. Por último, una propiedad
puede declararse abstracta. Las propiedades abstractas no definen ninguna
implementación en la clase, y las clases derivadas deben escribir su propia
implementación. Para obtener más información sobre estas opciones, vea Clases y
miembros de clase abstractos y sellados (Guía de programación de C#).
7 Nota
Ejemplos
En este ejemplo se muestran las propiedades de solo lectura, estáticas y de instancia.
Acepta el nombre del empleado desde el teclado, incrementa NumberOfEmployees en 1 y
muestra el nombre del empleado y el número.
C#
// A Constructor:
C#
class TestHiding
m1.Name = "John";
((Employee)m1).Name = "Mary";
/* Output:
*/
C#
C#
((Employee)m1).Name = "Mary";
Para obtener más información sobre cómo ocultar miembros, vea el Modificador new.
C#
get;
set;
//constructor
//constructor
class TestShapes
System.Console.WriteLine();
s.Area = area;
c.Area = area;
/* Example Output:
*/
Vea también
Propiedades
Propiedades de interfaz
Propiedades autoimplementadas
Propiedades de interfaces (Guía de
programación de C#)
Artículo • 09/02/2023 • Tiempo de lectura: 2 minutos
C#
// Property declaration:
string Name
get;
set;
Ejemplo
En este ejemplo, la interfaz IEmployee tiene una propiedad de lectura y escritura, Name , y
una propiedad de solo lectura, Counter . La clase Employee implementa la interfaz
IEmployee y usa estas dos propiedades. El programa lee el nombre de un nuevo
C#
string IEmployee.Name
set { }
C#
string IEmployee.Name
set { }
C#
string ICitizen.Name
set { }
C#
interface IEmployee
string Name
get;
set;
int Counter
get;
// constructor
C#
Employee.numberOfEmployees = int.Parse(System.Console.ReadLine());
e1.Name = System.Console.ReadLine();
Salida de ejemplo
Consola
Consulte también
Guía de programación de C#
Propiedades
Utilizar propiedades
Comparación entre propiedades e indizadores
Indizadores
Interfaces
Restringir la accesibilidad del descriptor
de acceso (Guía de programación de
C#)
Artículo • 15/02/2023 • Tiempo de lectura: 5 minutos
C#
get
return _name;
protected set
_name = value;
En este ejemplo, una propiedad denominada Name define un descriptor de acceso get y
set . El descriptor de acceso get recibe el nivel de accesibilidad de la propiedad, public
7 Nota
C#
protected set { }
get { return 0; }
protected set { }
get { return 0; }
Implementar interfaces
Al usar un descriptor de acceso para implementar una interfaz, este no puede tener un
modificador de acceso. En cambio, si implementa la interfaz con un descriptor de
acceso, como get , el otro descriptor de acceso puede tener un modificador de acceso,
como en el ejemplo siguiente:
C#
int TestProperty
get;
protected set { }
Ejemplo
En el ejemplo siguiente, se incluyen tres clases: BaseClass , DerivedClass y MainClass .
Hay dos propiedades en BaseClass , Name y Id en ambas clases. En el ejemplo, se
muestra cómo la propiedad Id en DerivedClass se puede ocultar con la propiedad Id
en BaseClass al usar un modificador de acceso restrictivo como protected o private. Por
tanto, cuando asigna valores a esta propiedad, se llama en su lugar a la propiedad en la
clase BaseClass . Si se reemplaza el modificador de acceso por public, la propiedad será
accesible.
C#
set { }
public string Id
set { }
get
return _name;
set
_name = value;
get
return _id;
set
_id = value;
class MainClass
b1.Name = "Mary";
d1.Name = "John";
b1.Id = "Mary123";
System.Console.ReadKey();
}
/* Output:
*/
Comentarios
Tenga en cuenta que, si reemplaza la declaración new private string Id por new public
string Id , obtendrá el resultado:
Vea también
Propiedades
Indizadores
Modificadores de acceso
Propiedades de solo inicialización
Propiedades necesarias
Procedimiento para declarar y usar
propiedades de lectura y escritura (Guía
de programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
Las propiedades proporcionan la comodidad de los miembros de datos públicos sin los
riesgos que provienen del acceso sin comprobar, sin controlar y sin proteger a los datos
de un objeto. Las propiedades declaran los descriptores de acceso: métodos especiales
que asignan y recuperan valores del miembro de datos subyacente. El descriptor de
acceso set permite que los miembros de datos se asignen, y el descriptor de acceso get
recupera los valores de los miembros de datos.
En este ejemplo se muestra una clase Person que tiene dos propiedades: Name (string) y
Age (int). Ambas propiedades proporcionan descriptores de acceso get y set , de
Ejemplo
C#
class Person
get
return _name;
set
_name = value;
get
return _age;
set
_age = value;
get
return _name;
private set
_name = value;
class TestPerson
// Print out the name and the age associated with the person:
person.Name = "Joe";
person.Age = 99;
person.Age += 1;
Console.ReadKey();
/* Output:
*/
Programación sólida
En el ejemplo anterior, las propiedades Name y Age son públicas e incluyen un descriptor
de acceso get y set . Los descriptores de acceso públicos permiten que cualquier
objeto lea y escriba estas propiedades. En cambio, a veces esto es conveniente para
excluir uno de los descriptores de acceso. Puede omitir el descriptor de acceso set para
que la propiedad sea de solo lectura:
C#
get
return _name;
private set
_name = value;
Una vez que se declaren las propiedades, pueden usarse como campos de la clase. Las
propiedades permiten una sintaxis natural cuando ambos obtienen y establecen el valor
de una propiedad, como se muestra en las instrucciones siguientes:
C#
person.Name = "Joe";
person.Age = 99;
En un método set de la propiedad está disponible una variable value especial. Esta
variable contiene el valor que el usuario ha especificado, por ejemplo:
C#
_name = value;
C#
person.Age += 1;
Si los métodos set y get independientes se han usado para modelar las propiedades,
el código equivalente puede tener este aspecto:
C#
person.SetAge(person.GetAge() + 1);
C#
Consulte también
Propiedades
El sistema de tipos de C#
Propiedades autoimplementadas (Guía
de programación de C#)
Artículo • 10/02/2023 • Tiempo de lectura: 2 minutos
Ejemplo
En el ejemplo siguiente se muestra una clase simple que tiene algunas propiedades
implementadas automáticamente:
C#
// Constructor
TotalPurchases = purchases;
Name = name;
CustomerId = id;
// Methods
class Program
// Modify a property.
cust1.TotalPurchases += 499.99;
C#
Para obtener más información, vea Procedimiento para implementar una clase ligera
con propiedades autoimplementadas.
Consulte también
Uso de propiedades implementadas automáticamente (regla de estilo IDE0032)
Propiedades
Modificadores
Procedimiento para implementar una
clase ligera con propiedades
autoimplementadas (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
En este ejemplo se muestra cómo crear una clase ligera inmutable que solo sirve para
encapsular un conjunto de propiedades autoimplementadas. Use este tipo de
construcción en lugar de un struct cuando deba utilizar una semántica de tipo de
referencia.
Declare solo el descriptor de acceso get, que hace que la propiedad sea inmutable
en cualquier lugar excepto en el constructor del tipo.
Declare un descriptor de acceso init en lugar de set , que hace que la propiedad se
pueda establecer solo en el constructor o mediante un inicializador de objeto.
Declare el descriptor de acceso set como private. La propiedad solo se puede
establecer dentro del tipo, pero es inmutable para los consumidores.
C#
class Contact
Name = contactName;
Address = contactAddress;
}
Ejemplo
En el siguiente ejemplo se muestran dos maneras de implementar una clase inmutable
que tenga propiedades autoimplementadas. Cada forma declara una de las propiedades
con un set privado y una de las propiedades solamente con un get . La primera clase
usa un constructor solo para inicializar las propiedades y la segunda clase utiliza un
método factory estático que llama a un constructor.
C#
class Contact
// Read-only property.
// Public constructor.
Name = contactName;
Address = contactAddress;
}
// Read-only property.
// Private constructor.
Name = contactName;
Address = contactAddress;
}
string[] addresses = {"123 Main St.", "345 Cypress Ave.", "678 1st
Ave",
select Contact2.CreateContact(names[i],
addresses[i]);
// CS0272:
Console.ReadKey();
/* Output:
*/
Consulte también
Propiedades
struct
Inicializadores de objeto y colección
Métodos (Guía de programación de C#)
Artículo • 14/02/2023 • Tiempo de lectura: 11 minutos
7 Nota
Firmas de método
Los métodos se declaran en una clase, struct o interfaz especificando el nivel de acceso,
como public o private , modificadores opcionales como abstract o sealed , el valor
devuelto, el nombre del método y cualquier parámetro de método. Todas estas partes
forman la firma del método.
) Importante
Los parámetros de método se encierran entre paréntesis y se separan por comas. Los
paréntesis vacíos indican que el método no requiere parámetros. Esta clase contiene
cuatro métodos:
C#
Acceso a métodos
Llamar a un método en un objeto es como acceder a un campo. Después del nombre
del objeto, agregue un punto, el nombre del método y paréntesis. Los argumentos se
enumeran entre paréntesis y están separados por comas. Los métodos de la clase
Motorcycle se pueden llamar como en el ejemplo siguiente:
C#
return 108.4;
moto.StartEngine();
moto.AddGas(15);
moto.Drive(5, 20);
C#
int numA = 4;
int Square(int i)
int input = i;
C#
Ahora, si se pasa un objeto basado en este tipo a un método, también se pasa una
referencia al objeto. En el ejemplo siguiente se pasa un objeto de tipo SampleRefType al
método ModifyObject :
C#
rt.value = 44;
ModifyObject(rt);
Console.WriteLine(rt.value);
obj.value = 33;
Para obtener más información sobre cómo pasar tipos de referencia por valor y por
referencia, vea Pasar parámetros Reference-Type (Guía de programación de C#) y Tipos
de referencia (Referencia de C#).
Valores devueltos
Los métodos pueden devolver un valor al autor de llamada. Si el tipo de valor devuelto
(el tipo que aparece antes del nombre de método) no es void , el método puede
devolver el valor mediante la instrucción return. Una instrucción con la palabra clave
return seguida de un valor que coincide con el tipo de valor devuelto devolverá este
valor al autor de llamada del método.
El valor se puede devolver al autor de la llamada por valor o por referencia. Los valores
se devuelven al autor de la llamada mediante referencia si la palabra clave ref se usa en
la firma del método y sigue cada palabra clave return . Por ejemplo, la siguiente firma
del método y la instrucción return indican que el método devuelve una variable
denominada estDistance mediante referencia al autor de la llamada.
C#
La palabra clave return también detiene la ejecución del método. Si el tipo de valor
devuelto es void , una instrucción return sin un valor también es útil para detener la
ejecución del método. Sin la palabra clave return , el método dejará de ejecutarse
cuando alcance el final del bloque de código. Los métodos con un tipo de valor
devuelto no nulo son necesarios para usar la palabra clave return para devolver un
valor. Por ejemplo, estos dos métodos utilizan la palabra clave return para devolver
enteros:
C#
class SimpleMath
result = obj.SquareANumber(result);
// The result is 9.
Console.WriteLine(result);
C#
// The result is 9.
Console.WriteLine(result);
Usar una variable local, en este caso, result , para almacenar un valor es opcional. La
legibilidad del código puede ser útil, o puede ser necesaria si debe almacenar el valor
original del argumento para todo el ámbito del método.
Para usar un valor devuelto mediante referencia de un método, debe declarar una
variable local de tipo ref si pretende modificar su valor. Por ejemplo, si el método
Planet.GetEstimatedDistance devuelve un valor Double mediante referencia, puede
definirlo como una variable local de tipo ref con código como el siguiente:
C#
C#
FillMatrix(matrix);
matrix[i, j] = -1;
Métodos asincrónicos
Mediante la característica asincrónica, puede invocar métodos asincrónicos sin usar
definiciones de llamada explícitas ni dividir manualmente el código en varios métodos o
expresiones lambda.
7 Nota
C#
class Program
Console.WriteLine($"Result: {result}");
await Task.Delay(100);
return 5;
// Example output:
// Result: 5
Un método aisncrónico no puede declarar ningún parámetro ref u out , pero puede
llamar a los métodos que tienen estos parámetros.
Para obtener más información sobre los métodos asincrónicos, consulte los artículos
Programación asincrónica con async y await y Tipos de valor devueltos asincrónicos.
C#
public Point Move(int dx, int dy) => new Point(x + dx, y + dy);
Iterators
Un iterador realiza una iteración personalizada en una colección, como una lista o
matriz. Un iterador utiliza la instrucción yield return para devolver cada elemento de uno
en uno. Cuando se alcanza una instrucción yield return , se recuerda la ubicación actual
en el código. La ejecución se reinicia desde esa ubicación la próxima vez que se llama el
iterador.
Llame a un iterador a partir del código de cliente mediante una instrucción foreach .
Consulte también
Guía de programación de C#
El sistema de tipos de C#
Modificadores de acceso
Clases estáticas y sus miembros
Herencia
Clases y miembros de clase abstractos y sellados
params
out
ref
Parámetros de métodos
Funciones locales (Guía de
programación de C#)
Artículo • 11/02/2023 • Tiempo de lectura: 10 minutos
Las funciones locales son métodos de un tipo que están anidados en otro miembro. Solo
se pueden llamar desde su miembro contenedor. Las funciones locales se pueden
declarar en y llamar desde:
7 Nota
Las funciones locales aclaran la intención del código. Cualquiera que lea el código
puede ver que solo el método que lo contiene puede llamar al método. Para los
proyectos de equipo, también hacen que sea imposible que otro desarrollador llame
erróneamente al método de forma directa desde cualquier otro punto de la clase o
estructura.
C#
async
unsafe
static Una función local estática no puede capturar variables locales o el estado de
la instancia.
extern Una función local externa debe ser static .
Todas las variables locales que se definen en el miembro contenedor, incluidos sus
parámetros de método, son accesibles en la función local no estática.
C#
return text;
A partir de C# 9.0, puede aplicar atributos a una función local, sus parámetros y
parámetros de tipo, como se muestra en el ejemplo siguiente:
C#
#nullable enable
if (IsValid(line))
// Processing logic...
C#
Console.WriteLine("Retrieved enumerator...");
Console.Write($"{x} ");
if (i % 2 == 1)
yield return i;
//
// Retrieved enumerator...
// at IteratorWithoutLocalExample.Main() in IteratorWithoutLocal.cs:line
11
C#
Console.WriteLine("Retrieved enumerator...");
Console.Write($"{x} ");
return GetOddSequenceEnumerator();
IEnumerable<int> GetOddSequenceEnumerator()
if (i % 2 == 1)
yield return i;
//
// at IteratorWithLocalExample.Main() in IteratorWithLocal.cs:line 8
C#
return nthFactorial(n);
? 1
C#
? 1
return nthFactorial(n);
Nomenclatura
Las funciones locales se nombran explícitamente como métodos. Las expresiones
lambda son métodos anónimos y deben asignarse a variables de un tipo delegate ,
normalmente los tipos Action o Func . Cuando se declara una función local, el proceso
es como escribir un método normal: se declaran un tipo de valor devuelto y una
signatura de función.
A partir de C# 10, algunas expresiones lambda tienen un tipo natural, que permite al
compilador deducir el tipo de valor devuelto y los tipos de parámetro de la expresión
lambda.
Asignación definitiva
Las expresiones lambda son objetos que se declaran y se asignan en tiempo de
ejecución. Para poder usar una expresión lambda, debe asignarse definitivamente: se
debe declarar la variable Action / Func a la que se va a asignar y luego asignar a esta la
expresión lambda. Observe que LambdaFactorial debe declarar e inicializar la expresión
lambda nthFactorial antes de definirla. De no hacerlo, se produce un error en tiempo
de compilación por hacer referencia a nthFactorial antes de asignarlo.
Si se declara una función local y solo se hace referencia a ella llamándola como un
método, no se convertirá en un delegado.
Captura de variables
Las reglas de asignación definitiva también afectan a las variables capturadas por la
función local o la expresión lambda. El compilador puede efectuar un análisis estático
que permite a las funciones locales asignar definitivamente variables capturadas en el
ámbito de inclusión. Considere este ejemplo:
C#
int M()
int y;
LocalFunction();
return y;
Observe que cuando una función local captura variables en el ámbito de inclusión, la
función local se implementa como un tipo delegado.
Asignaciones de montón
Dependiendo de su uso, las funciones locales pueden evitar las asignaciones de montón
que siempre son necesarias para las expresiones lambda. Si una función local no se
convierte nunca en un delegado y ninguna de las variables capturadas por la función
local se captura con otras expresiones lambda o funciones locales que se han convertido
en delegados, el compilador puede evitar las asignaciones de montón.
C#
if (string.IsNullOrWhiteSpace(address))
if (index < 0)
if (string.IsNullOrWhiteSpace(name))
};
La clausura de esta expresión lambda contiene las variables address , index y name . En
el caso de las funciones locales, el objeto que implementa el cierre puede ser un tipo
struct . Luego, ese tipo de estructura se pasaría por referencia a la función local. Esta
Sugerencia
Habilite la regla de estilo de código de .NET IDE0062 para asegurarse de que las
funciones locales siempre estén marcadas como static .
7 Nota
La función local equivalente de este método también usa una clase para el cierre. Si
el cierre de una función local se implementa como class o struct es un detalle de
implementación. Una función local puede usar struct mientras una expresión
lambda siempre usará class .
C#
if (string.IsNullOrWhiteSpace(address))
if (index < 0)
if (string.IsNullOrWhiteSpace(name))
C#
if (!input.Any())
return LowercaseIterator();
IEnumerable<string> LowercaseIterator()
La instrucción yield return no se permite en las expresiones lambda; vea el Error del
compilador CS1621.
Aunque las funciones locales pueden parecer redundantes con respecto a las
expresiones lambda, en realidad tienen finalidades y usos diferentes. Las funciones
locales son más eficaces si se quiere escribir una función a la que solo se llame desde el
contexto de otro método.
Vea también
Uso de la función local en lugar de lambda (regla de estilo IDE0039)
Métodos
Instrucciones de declaración
Artículo • 18/02/2023 • Tiempo de lectura: 8 minutos
C#
) Importante
Cuando var se usa con tipos de referencia que aceptan valores NULL, siempre
implica un tipo de referencia que acepta valores NULL, aunque el tipo de expresión
no los acepte. El análisis de null-state del compilador protege frente a la
desreferenciación de un posible valor null . Si la variable nunca se asigna a una
expresión que pueda ser NULL, el compilador no emitirá ninguna advertencia. Si
asigna la variable a una expresión que podría ser NULL, debe probar que no sea
NULL antes de desreferenciarla para evitar advertencias.
Un uso común de la palabra clave var es con las expresiones de invocación del
constructor. El uso de var permite no repetir un nombre de tipo en una declaración de
variable y una creación de instancias de objeto, como se muestra en el ejemplo
siguiente:
C#
var xs = new List<int>();
A partir de C# 9.0, se puede usar una expresión new de con tipo de destino como
alternativa:
C#
List<int> xs = new();
List<int>? ys = new();
C#
select word;
Console.WriteLine(s);
C#
C#
Puede acceder a un valor por referencia de la misma manera. En algunos casos, acceder
a un valor por referencia aumenta el rendimiento, ya que evita una operación de copia
potencialmente cara. Por ejemplo, en la instrucción siguiente se muestra cómo es
posible definir un valor local de referencia que se usa para hacer referencia a un valor.
C#
La palabra clave ref se usa antes de la declaración de variable local y antes del valor en
el segundo ejemplo. Si no se incluyen ambas palabras clave ref en la asignación y
declaración de la variable en ambos ejemplos, se produce el error del
compilador CS8172, "No se puede inicializar una variable por referencia con un valor".
C#
En el ejemplo siguiente, se define una clase NumberStore que almacena una matriz de
valores enteros. El método FindNumber devuelve por referencia el primer número que es
mayor o igual que el número que se pasa como argumento. Si ningún número es mayor
o igual que el argumento, el método devuelve el número en el índice 0.
C#
using System;
class NumberStore
C#
value *= 2;
Sin que se admitan los valores devueltos de referencia, este tipo de operación se realiza
al devolver el índice del elemento de matriz junto con su valor. Después, el autor de la
llamada puede usar este índice para modificar el valor en una llamada al método
independiente. En cambio, el autor de la llamada también puede modificar el índice
para tener acceso a otros valores de matriz y, posiblemente, modificarlos.
El siguiente ejemplo muestra cómo se podría reescribir el método FindNumber para usar
la reasignación de variable local ref:
C#
using System;
class NumberStore
ctr--;
Esta segunda versión es más eficaz con secuencias más largas en escenarios donde el
número buscado está más cerca del final de la matriz, ya que en la matriz se itera desde
el final hacia el principio, lo que hace que se examinen menos elementos.
C#
Las declaraciones readonly ref y readonly ref readonly solo son válidas en campos
ref de ref struct .
Vea también
ref (palabra clave)
Cómo evitar asignaciones
Preferencias "var" (reglas de estilo IDE0007 e IDE0008)
Referencia de C#
Relaciones entre tipos en las operaciones de consulta LINQ
C# 11: modificador con ámbito
Parámetros de métodos (Referencia de
C#)
Artículo • 18/02/2023 • Tiempo de lectura: 10 minutos
En C#, los argumentos se pueden pasar a parámetros por valor o por referencia.
Recuerde que los tipos de C# pueden ser tipos de referencia ( class ) o tipos de valor
( struct ):
Como un struct es un tipo de valor, cuando pasa un struct mediante valor a un método,
el método recibe y funciona en una copia del argumento struct. El método no tiene
acceso al struct original en el método de llamada y, por lo tanto, no puede cambiarlo de
ninguna manera. El método solo puede cambiar la copia.
class TheClass
struct TheStruct
class TestClassAndStruct
c.willIChange = "Changed";
s.willIChange = "Changed";
ClassTaker(testClass);
StructTaker(testStruct);
Console.ReadKey();
/* Output:
*/
Si el método asigna el parámetro para hacer referencia a otro objeto, esos cambios
no son visibles desde el autor de la llamada.
Si el método modifica el estado del objeto al que hace referencia el parámetro,
esos cambios no son visibles desde el autor de la llamada.
C#
int n = 5;
System.Console.ReadKey();
x *= x;
/* Output:
*/
Si el método asigna el parámetro para hacer referencia a otro objeto, esos cambios
no son visibles desde el autor de la llamada.
Si el método modifica el estado del objeto al que hace referencia el parámetro,
esos cambios son visibles desde el autor de la llamada.
El ejemplo siguiente es el mismo que el anterior, salvo que el argumento se pasa como
un parámetro ref . El valor del argumento subyacente, n , se cambia cuando se modifica
x en el método.
C#
int n = 5;
System.Console.ReadKey();
x *= x;
/* Output:
*/
Si el método asigna el parámetro para hacer referencia a otro objeto, esos cambios
no son visibles desde el autor de la llamada.
Si el método modifica el estado del objeto al que hace referencia el parámetro,
esos cambios son visibles desde el autor de la llamada.
C#
int[] arr = { 1, 4, 5 };
Change(arr);
pArray = new int[5] { -3, -1, -2, -3, -4 }; // This change is local.
/* Output:
Inside Main, before calling the method, the first element is: 1
Inside Main, after calling the method, the first element is: 888
*/
El ejemplo siguiente es el mismo que el anterior, salvo que la palabra clave ref se
agrega a la llamada y al encabezado de método. Los cambios que tengan lugar en el
método afectan a la variable original en el programa que realiza la llamada.
C#
int[] arr = { 1, 4, 5 };
Change(ref arr);
pArray[0] = 888;
/* Output:
Inside Main, before calling the method, the first element is: 1
Inside Main, after calling the method, the first element is: -3
*/
Todos los cambios que tienen lugar dentro del método afectan a la matriz original en
Main . De hecho, la matriz original se reasigna mediante el operador new . Por tanto,
Modificadores
Los parámetros declarados para un método sin in, ref o out se pasan al método llamado
por valor. Los modificadores ref , in y out difieren en las reglas de asignación:
Esta sección describe las palabras clave que puede usar para declarar parámetros de
métodos:
Vea también
Referencia de C#
Palabras clave de C#
Listas de argumentos en la especificación del lenguaje C#. La especificación del
lenguaje es la fuente definitiva de la sintaxis y el uso de C#.
Parámetros de métodos (Referencia de
C#)
Artículo • 18/02/2023 • Tiempo de lectura: 10 minutos
En C#, los argumentos se pueden pasar a parámetros por valor o por referencia.
Recuerde que los tipos de C# pueden ser tipos de referencia ( class ) o tipos de valor
( struct ):
Como un struct es un tipo de valor, cuando pasa un struct mediante valor a un método,
el método recibe y funciona en una copia del argumento struct. El método no tiene
acceso al struct original en el método de llamada y, por lo tanto, no puede cambiarlo de
ninguna manera. El método solo puede cambiar la copia.
class TheClass
struct TheStruct
class TestClassAndStruct
c.willIChange = "Changed";
s.willIChange = "Changed";
ClassTaker(testClass);
StructTaker(testStruct);
Console.ReadKey();
/* Output:
*/
Si el método asigna el parámetro para hacer referencia a otro objeto, esos cambios
no son visibles desde el autor de la llamada.
Si el método modifica el estado del objeto al que hace referencia el parámetro,
esos cambios no son visibles desde el autor de la llamada.
C#
int n = 5;
System.Console.ReadKey();
x *= x;
/* Output:
*/
Si el método asigna el parámetro para hacer referencia a otro objeto, esos cambios
no son visibles desde el autor de la llamada.
Si el método modifica el estado del objeto al que hace referencia el parámetro,
esos cambios son visibles desde el autor de la llamada.
El ejemplo siguiente es el mismo que el anterior, salvo que el argumento se pasa como
un parámetro ref . El valor del argumento subyacente, n , se cambia cuando se modifica
x en el método.
C#
int n = 5;
System.Console.ReadKey();
x *= x;
/* Output:
*/
Si el método asigna el parámetro para hacer referencia a otro objeto, esos cambios
no son visibles desde el autor de la llamada.
Si el método modifica el estado del objeto al que hace referencia el parámetro,
esos cambios son visibles desde el autor de la llamada.
C#
int[] arr = { 1, 4, 5 };
Change(arr);
pArray = new int[5] { -3, -1, -2, -3, -4 }; // This change is local.
/* Output:
Inside Main, before calling the method, the first element is: 1
Inside Main, after calling the method, the first element is: 888
*/
El ejemplo siguiente es el mismo que el anterior, salvo que la palabra clave ref se
agrega a la llamada y al encabezado de método. Los cambios que tengan lugar en el
método afectan a la variable original en el programa que realiza la llamada.
C#
int[] arr = { 1, 4, 5 };
Change(ref arr);
pArray[0] = 888;
/* Output:
Inside Main, before calling the method, the first element is: 1
Inside Main, after calling the method, the first element is: -3
*/
Todos los cambios que tienen lugar dentro del método afectan a la matriz original en
Main . De hecho, la matriz original se reasigna mediante el operador new . Por tanto,
Modificadores
Los parámetros declarados para un método sin in, ref o out se pasan al método llamado
por valor. Los modificadores ref , in y out difieren en las reglas de asignación:
Esta sección describe las palabras clave que puede usar para declarar parámetros de
métodos:
Vea también
Referencia de C#
Palabras clave de C#
Listas de argumentos en la especificación del lenguaje C#. La especificación del
lenguaje es la fuente definitiva de la sintaxis y el uso de C#.
Parámetros de métodos (Referencia de
C#)
Artículo • 18/02/2023 • Tiempo de lectura: 10 minutos
En C#, los argumentos se pueden pasar a parámetros por valor o por referencia.
Recuerde que los tipos de C# pueden ser tipos de referencia ( class ) o tipos de valor
( struct ):
Como un struct es un tipo de valor, cuando pasa un struct mediante valor a un método,
el método recibe y funciona en una copia del argumento struct. El método no tiene
acceso al struct original en el método de llamada y, por lo tanto, no puede cambiarlo de
ninguna manera. El método solo puede cambiar la copia.
class TheClass
struct TheStruct
class TestClassAndStruct
c.willIChange = "Changed";
s.willIChange = "Changed";
ClassTaker(testClass);
StructTaker(testStruct);
Console.ReadKey();
/* Output:
*/
Si el método asigna el parámetro para hacer referencia a otro objeto, esos cambios
no son visibles desde el autor de la llamada.
Si el método modifica el estado del objeto al que hace referencia el parámetro,
esos cambios no son visibles desde el autor de la llamada.
C#
int n = 5;
System.Console.ReadKey();
x *= x;
/* Output:
*/
Si el método asigna el parámetro para hacer referencia a otro objeto, esos cambios
no son visibles desde el autor de la llamada.
Si el método modifica el estado del objeto al que hace referencia el parámetro,
esos cambios son visibles desde el autor de la llamada.
El ejemplo siguiente es el mismo que el anterior, salvo que el argumento se pasa como
un parámetro ref . El valor del argumento subyacente, n , se cambia cuando se modifica
x en el método.
C#
int n = 5;
System.Console.ReadKey();
x *= x;
/* Output:
*/
Si el método asigna el parámetro para hacer referencia a otro objeto, esos cambios
no son visibles desde el autor de la llamada.
Si el método modifica el estado del objeto al que hace referencia el parámetro,
esos cambios son visibles desde el autor de la llamada.
C#
int[] arr = { 1, 4, 5 };
Change(arr);
pArray = new int[5] { -3, -1, -2, -3, -4 }; // This change is local.
/* Output:
Inside Main, before calling the method, the first element is: 1
Inside Main, after calling the method, the first element is: 888
*/
El ejemplo siguiente es el mismo que el anterior, salvo que la palabra clave ref se
agrega a la llamada y al encabezado de método. Los cambios que tengan lugar en el
método afectan a la variable original en el programa que realiza la llamada.
C#
int[] arr = { 1, 4, 5 };
Change(ref arr);
pArray[0] = 888;
/* Output:
Inside Main, before calling the method, the first element is: 1
Inside Main, after calling the method, the first element is: -3
*/
Todos los cambios que tienen lugar dentro del método afectan a la matriz original en
Main . De hecho, la matriz original se reasigna mediante el operador new . Por tanto,
Modificadores
Los parámetros declarados para un método sin in, ref o out se pasan al método llamado
por valor. Los modificadores ref , in y out difieren en las reglas de asignación:
Esta sección describe las palabras clave que puede usar para declarar parámetros de
métodos:
Vea también
Referencia de C#
Palabras clave de C#
Listas de argumentos en la especificación del lenguaje C#. La especificación del
lenguaje es la fuente definitiva de la sintaxis y el uso de C#.
Parámetros de métodos (Referencia de
C#)
Artículo • 18/02/2023 • Tiempo de lectura: 10 minutos
En C#, los argumentos se pueden pasar a parámetros por valor o por referencia.
Recuerde que los tipos de C# pueden ser tipos de referencia ( class ) o tipos de valor
( struct ):
Como un struct es un tipo de valor, cuando pasa un struct mediante valor a un método,
el método recibe y funciona en una copia del argumento struct. El método no tiene
acceso al struct original en el método de llamada y, por lo tanto, no puede cambiarlo de
ninguna manera. El método solo puede cambiar la copia.
class TheClass
struct TheStruct
class TestClassAndStruct
c.willIChange = "Changed";
s.willIChange = "Changed";
ClassTaker(testClass);
StructTaker(testStruct);
Console.ReadKey();
/* Output:
*/
Si el método asigna el parámetro para hacer referencia a otro objeto, esos cambios
no son visibles desde el autor de la llamada.
Si el método modifica el estado del objeto al que hace referencia el parámetro,
esos cambios no son visibles desde el autor de la llamada.
C#
int n = 5;
System.Console.ReadKey();
x *= x;
/* Output:
*/
Si el método asigna el parámetro para hacer referencia a otro objeto, esos cambios
no son visibles desde el autor de la llamada.
Si el método modifica el estado del objeto al que hace referencia el parámetro,
esos cambios son visibles desde el autor de la llamada.
El ejemplo siguiente es el mismo que el anterior, salvo que el argumento se pasa como
un parámetro ref . El valor del argumento subyacente, n , se cambia cuando se modifica
x en el método.
C#
int n = 5;
System.Console.ReadKey();
x *= x;
/* Output:
*/
Si el método asigna el parámetro para hacer referencia a otro objeto, esos cambios
no son visibles desde el autor de la llamada.
Si el método modifica el estado del objeto al que hace referencia el parámetro,
esos cambios son visibles desde el autor de la llamada.
C#
int[] arr = { 1, 4, 5 };
Change(arr);
pArray = new int[5] { -3, -1, -2, -3, -4 }; // This change is local.
/* Output:
Inside Main, before calling the method, the first element is: 1
Inside Main, after calling the method, the first element is: 888
*/
El ejemplo siguiente es el mismo que el anterior, salvo que la palabra clave ref se
agrega a la llamada y al encabezado de método. Los cambios que tengan lugar en el
método afectan a la variable original en el programa que realiza la llamada.
C#
int[] arr = { 1, 4, 5 };
Change(ref arr);
pArray[0] = 888;
/* Output:
Inside Main, before calling the method, the first element is: 1
Inside Main, after calling the method, the first element is: -3
*/
Todos los cambios que tienen lugar dentro del método afectan a la matriz original en
Main . De hecho, la matriz original se reasigna mediante el operador new . Por tanto,
Modificadores
Los parámetros declarados para un método sin in, ref o out se pasan al método llamado
por valor. Los modificadores ref , in y out difieren en las reglas de asignación:
Esta sección describe las palabras clave que puede usar para declarar parámetros de
métodos:
Vea también
Referencia de C#
Palabras clave de C#
Listas de argumentos en la especificación del lenguaje C#. La especificación del
lenguaje es la fuente definitiva de la sintaxis y el uso de C#.
Variables locales con asignación
implícita de tipos (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 5 minutos
Las variables locales pueden declararse sin proporcionar un tipo explícito. La palabra
clave var indica al compilador que infiera el tipo de la variable a partir de la expresión
de la derecha de la instrucción de inicialización. El tipo inferido puede ser un tipo
integrado, un tipo anónimo, un tipo definido por el usuario o un tipo definido en la
biblioteca de clases .NET. Para obtener más información sobre cómo inicializar las
matrices con var , vea Matrices con tipo implícito.
Los ejemplos siguientes muestran distintas formas en que se pueden declarar variables
locales con var :
C#
// i is compiled as an int
var i = 5;
// s is compiled as a string
var s = "Hello";
// a is compiled as int[]
var a = new[] { 0, 1, 2 };
// or perhaps IQueryable<Customer>
var expr =
from c in customers
where c.City == "London"
select c;
Es importante comprender que la palabra clave var no significa "variant" ni indica que
la variable esté débilmente tipada o esté enlazada en tiempo de ejecución. Solo significa
que el compilador determina y asigna el tipo más adecuado.
C#
C#
C#
Para obtener más información, vea Procedimiento para usar matrices y variables locales
con tipo implícito en expresiones de consulta.
Desde el punto de vista del código fuente, un tipo anónimo no tiene nombre. Por lo
tanto, si una variable de consulta se ha inicializado con var , la única manera de tener
acceso a las propiedades de la secuencia de objetos devuelta consiste en usar var
como el tipo de la variable de iteración en la instrucción foreach .
C#
class ImplicitlyTypedLocals2
var upperLowerWords =
from w in words
/* Outputs:
*/
Comentarios
Las siguientes restricciones se aplican a las declaraciones de variable con tipo implícito:
var solo se puede usar cuando una variable local se declara e inicializa en la
misma instrucción; la variable no se puede inicializar en null ni en un grupo de
métodos o una función anónima.
El establecimiento de tipos implícitos con la palabra clave var solo puede aplicarse a
variables en el ámbito del método local. El establecimiento de tipos implícitos no está
disponible para los campos de clase ya que el compilador C# encontraría una paradoja
lógica al procesar el código: el compilador necesita conocer el tipo de campo, pero no
puede determinar el tipo hasta que se analiza la expresión de asignación, y no se puede
evaluar la expresión sin conocer el tipo. Observe el código siguiente:
C#
bookTitles es un campo de clase dado el tipo var . Como el campo no tiene ninguna
expresión que evaluar, es imposible que el compilador pueda inferir el tipo bookTitles
que se supone que es. Además, agregar una expresión al campo (como se haría con una
variable local) también es suficiente:
C#
Es posible que var también pueda resultar útil con expresiones de consulta en las que
es difícil determinar el tipo construido exacto de la variable de consulta. Esto puede
ocurrir con las operaciones de agrupamiento y ordenación.
La palabra clave var también puede ser útil cuando resulte tedioso escribir el tipo
específico de la variable en el teclado, o sea obvio o no aumente la legibilidad del
código. Un ejemplo en el que var resulta útil de esta manera es cuando se usa con
tipos genéricos anidados, como los que se emplean con las operaciones de grupo. En la
siguiente consulta, el tipo de la variable de consulta es IEnumerable<IGrouping<string,
Student>> . Siempre que usted y otras personas que deban mantener el código
comprendan esto, no hay ningún problema con el uso de tipos implícitos por
comodidad y brevedad.
C#
// Same as previous example except we use the entire last name as a key.
var studentQuery3 =
El uso de var ayuda a simplificar el código, pero debe quedar restringido a los casos en
los que sea necesario, o cuando haga que el código sea más fácil de leer. Para obtener
más información sobre cuándo usar var correctamente, vea la sección Variables locales
con asignación implícita de tipos en el artículo sobre directrices de codificación de C#.
Vea también
Referencia de C#
Matrices con tipo implícito
Procedimiento para usar matrices y variables locales con tipo implícito en
expresiones de consulta
Tipos anónimos
Inicializadores de objeto y colección
var
LINQ en C#
LINQ (Language Integrated Query)
Instrucciones de iteración
using (instrucción)
Procedimiento para usar matrices y
variables locales con tipo implícito en
expresiones de consulta (Guía de
programación de C#)
Artículo • 11/02/2023 • Tiempo de lectura: 2 minutos
Puede usar variables locales con tipo implícito siempre que quiera que el compilador
determine el tipo de una variable local. Debe usar variables locales con tipo implícito
para almacenar tipos anónimos, que a menudo se usan en las expresiones de consulta.
En los ejemplos siguientes, se muestran los usos obligatorios y opcionales de las
variables locales con tipo implícito en las consultas.
Las variables locales con tipo implícito se declaran mediante la palabra clave contextual
var. Para obtener más información, vea Variables locales con asignación implícita de
tipos y Matrices con asignación implícita de tipos.
Ejemplos
En el ejemplo siguiente, se muestra un escenario común en el que la palabra clave var
es necesaria: una expresión de consulta que genera una secuencia de tipos anónimos.
En este escenario, la variable de consulta y la variable de iteración en la instrucción
foreach deben escribirse de forma implícita mediante el uso de var porque no se tiene
acceso a un nombre de tipo para el tipo anónimo. Para obtener más información sobre
los tipos anónimos, vea Tipos anónimos.
C#
// System.Collections.Generic.IEnumerable<????>.
var studentQuery =
En el ejemplo siguiente, se usa la palabra clave var en una situación similar, pero en la
que el uso de var es opcional. Dado que student.LastName es una cadena, la ejecución
de la consulta devuelve una secuencia de cadenas. Por tanto, el tipo de queryId podría
declararse como System.Collections.Generic.IEnumerable<string> en lugar de var . La
palabra clave var se usa por comodidad. En el ejemplo, la variable de iteración en la
instrucción foreach se escribe de forma explícita como una cadena, pero se podría
declarar mediante var . Dado que el tipo de la variable de iteración no es un tipo
anónimo, el uso de var es opcional, no es obligatorio. Recuerde que var no es un tipo,
sino una instrucción para que el compilador deduzca y asigne el tipo.
C#
// System.Collections.Generic.IEnumerable<string>
// instead of var.
var queryId =
select student.LastName;
Vea también
Guía de programación de C#
Métodos de extensión
LINQ (Language Integrated Query)
LINQ en C#
Métodos de extensión (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 11 minutos
Los métodos de extensión permiten "agregar" métodos a los tipos existentes sin crear
un nuevo tipo derivado, recompilar o modificar de otra manera el tipo original. Los
métodos de extensión son métodos estáticos, pero se les llama como si fueran métodos
de instancia en el tipo extendido. En el caso del código de cliente escrito en C#, F# y
Visual Basic, no existe ninguna diferencia aparente entre llamar a un método de
extensión y llamar a los métodos definidos en un tipo.
Los métodos de extensión más comunes son los operadores de consulta LINQ estándar,
que agregan funciones de consulta a los tipos System.Collections.IEnumerable y
System.Collections.Generic.IEnumerable<T> existentes. Para usar los operadores de
consulta estándar, inclúyalos primero en el ámbito con una directiva using System.Linq .
A partir de ese momento, cualquier tipo que implemente IEnumerable<T> parecerá
tener métodos de instancia como GroupBy, OrderBy, Average, etc. Puede ver estos
métodos adicionales en la finalización de instrucciones de IntelliSense al escribir "punto"
después de una instancia de un tipo IEnumerable<T>, como List<T> o Array.
Ejemplo de OrderBy
En el ejemplo siguiente se muestra cómo llamar al método OrderBy de operador de
consulta estándar en una matriz de enteros. La expresión entre paréntesis es una
expresión lambda. Muchos operadores de consulta estándar toman expresiones lambda
como parámetros, pero no es un requisito para los métodos de extensión. Para obtener
más información, vea Expresiones lambda.
C#
class ExtensionMethods2
//Output: 10 15 21 26 39 45
Los métodos de extensión se definen como métodos estáticos, pero se les llama usando
la sintaxis de método de instancia. Su primer parámetro especifica en qué tipo funciona
el método. El parámetro va precedido del modificador this. Los métodos de extensión
únicamente se encuentran dentro del ámbito cuando el espacio de nombres se importa
explícitamente en el código fuente con una directiva using .
C#
namespace ExtensionMethods
StringSplitOptions.RemoveEmptyEntries).Length;
C#
using ExtensionMethods;
C#
int i = s.WordCount();
C#
int i = MyExtensions.WordCount(s);
El código de C# anterior:
C#
using System.Linq;
(Puede que haya que agregar también una referencia a System.Core.dll). Observará que
los operadores de consulta estándar aparecen ahora en IntelliSense como métodos
adicionales disponibles para la mayoría de los tipos IEnumerable<T>.
Ejemplo
En el ejemplo siguiente se muestran las reglas que el compilador de C# sigue para
determinar si se va a enlazar una llamada a método a un método de instancia del tipo o
a un método de extensión. La clase estática Extensions contiene métodos de extensión
definidos para cualquier tipo que implemente IMyInterface . Las clases A , B y C
implementan la interfaz.
C#
namespace DefineIMyInterface
using System;
void MethodB();
namespace Extensions
using System;
using DefineIMyInterface;
Console.WriteLine
Console.WriteLine
Console.WriteLine
// Define three classes that implement IMyInterface, and then use them to
test
namespace ExtensionMethodsDemo1
using System;
using Extensions;
using DefineIMyInterface;
class A : IMyInterface
class B : IMyInterface
class C : IMyInterface
Console.WriteLine("C.MethodA(object obj)");
class ExtMethodDemo
A a = new A();
B b = new B();
C c = new C();
a.MethodA("hello"); // Extension.MethodA(IMyInterface,
string)
// to MethodB.
a.MethodB(); // A.MethodB()
// method calls.
b.MethodA(1); // B.MethodA(int)
b.MethodB(); // B.MethodB()
b.MethodA("hello"); // Extension.MethodA(IMyInterface,
string)
// method calls.
c.MethodA(1); // C.MethodA(object)
c.MethodA("hello"); // C.MethodA(object)
c.MethodB(); // C.MethodB()
/* Output:
A.MethodB()
B.MethodA(int i)
B.MethodB()
C.MethodA(object obj)
C.MethodA(object obj)
C.MethodB()
*/
Funcionalidad de colección
En el pasado, era habitual crear "Clases de colección" que implementaban la interfaz
System.Collections.Generic.IEnumerable<T> para un tipo especificado e incluían una
funcionalidad que actuaba en colecciones de ese tipo. Aunque no hay ningún problema
con la creación de este tipo de objeto de colección, se puede lograr la misma
funcionalidad mediante una extensión en System.Collections.Generic.IEnumerable<T>.
Las extensiones tienen la ventaja de permitir que se llame a la funcionalidad desde
cualquier colección como System.Array o System.Collections.Generic.List<T> que
implementa System.Collections.Generic.IEnumerable<T> en ese tipo. Encontrará un
ejemplo de esto mediante una matriz de Int32 anteriormente en este artículo.
C#
La ampliación de tipos predefinidos puede ser difícil con los tipos struct , ya que se
pasan en función del valor a los métodos. Eso significa que los cambios en la estructura
se realizan en una copia de la misma. Esos cambios dejarán de verse una vez que se
salga del método de extensión. Puede agregar el modificador ref al primer argumento
de un método de extensión. La adición del modificador ref significa que el primer
argumento se pasa por referencia. Esto le permite escribir métodos de extensión que
cambian el estado de la estructura que se amplía.
Instrucciones generales
Aunque sigue considerándose preferible agregar la funcionalidad modificando un
código del objeto o derivando un nuevo tipo siempre que sea razonable y posible
hacerlo, los métodos de extensión se han convertido en una opción fundamental para
crear una funcionalidad reutilizable en todo el ecosistema .NET. Para esas ocasiones en
las que no cuente con el control del origen original, si un objeto derivado es inadecuado
o imposible, o la funcionalidad no se debe exponer más allá de su ámbito aplicable, los
métodos de extensión son una opción excelente.
Para obtener más información sobre los tipos derivados, consulte Herencia.
Al usar un método de extensión para ampliar un tipo cuyo código fuente no está bajo
su control, se corre el riesgo de que un cambio en la implementación del tipo
interrumpa el método de extensión.
Vea también
Guía de programación de C#
Ejemplos de programación en paralelo (incluyen numerosos métodos de extensión
de ejemplo)
Expresiones lambda
Información general sobre operadores de consulta estándar
Conversion rules for Instance parameters and their impact (Reglas de conversión
para los parámetros de instancia y su impacto)
Extension methods Interoperability between languages (Interoperabilidad de los
métodos de extensión entre lenguajes)
Extension methods and Curried Delegates (Métodos de extensión y delegados
currificados)
Extension method Binding and Error reporting (Enlazar métodos de extensión y
notificación de errores)
Procedimiento para implementar e
invocar un método de extensión
personalizado (Guía de programación
de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
En este tema se muestra cómo implementar sus propios métodos de extensión para
cualquier tipo de .NET. El código de cliente puede usar los métodos de extensión
agregando una referencia a la DLL que los contiene y agregando una directiva using
que especifique el espacio de nombres en el que se definen los métodos de extensión.
La clase debe estar visible para el código de cliente. Para obtener más información
sobre las reglas de accesibilidad, vea Modificadores de acceso.
3. El primer parámetro del método especifica el tipo en el que opera el método; debe
ir precedido del modificador this.
Ejemplo
En el ejemplo siguiente se implementa un método de extensión denominado WordCount
en la clase CustomExtensions.StringExtension . El método opera en la clase String, que
se especifica como el primer parámetro de método. El espacio de nombres
CustomExtensions se importa en el espacio de nombres de la aplicación y se llama al
C#
using System.Linq;
using System.Text;
using System;
namespace CustomExtensions
namespace Extension_Methods_Simple
using CustomExtensions;
class Program
string s = "The quick brown fox jumped over the lazy dog.";
int i = s.WordCount();
Seguridad de .NET
Los métodos de extensión no presentan ninguna vulnerabilidad de seguridad específica.
No se pueden usar nunca para suplantar los métodos existentes en un tipo, porque
todos los conflictos de nombres se resuelven a favor de la instancia o del método
estático definido por el tipo en cuestión. Los métodos de extensión no pueden tener
acceso a los datos privados de la clase extendida.
Vea también
Guía de programación de C#
Métodos de extensión
LINQ (Language Integrated Query)
Clases estáticas y sus miembros
protected
internal
public
this
namespace
Procedimiento para crear un método
nuevo para una enumeración (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Ejemplo
En el ejemplo siguiente, la enumeración Grades representa las posibles calificaciones
con letras que un alumno puede recibir en una clase. Un método de extensión
denominado Passing se agrega al tipo Grades para que cada instancia de ese tipo
"sepa" ahora si representa una calificación de aprobado o no.
C#
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
namespace EnumExtension
class Program
Grades g1 = Grades.D;
Grades g2 = Grades.F;
Console.WriteLine("First {0} a passing grade.", g1.Passing() ?
"is" : "is not");
Extensions.minPassing = Grades.C;
/* Output:
*/
Tenga en cuenta que la clase Extensions también contiene una variable estática que se
actualiza dinámicamente y que el valor devuelto del método de extensión refleja el valor
actual de esa variable. Esto demuestra que, en segundo plano, los métodos de extensión
se invocan directamente en la clase estática en donde se definen.
Vea también
Guía de programación de C#
Métodos de extensión
Argumentos opcionales y con nombre
(Guía de programación de C#)
Artículo • 25/02/2023 • Tiempo de lectura: 7 minutos
Cuando se usan argumentos opcionales y con nombre, los argumentos se evalúan por
el orden en que aparecen en la lista de argumentos, no en la lista de parámetros.
Los parámetros opcionales y con nombre permiten proporcionar argumentos para los
parámetros seleccionados. Esta funcionalidad facilita enormemente las llamadas a
interfaces COM, como las API de automatización de Microsoft Office.
C#
Si no recuerda el orden de los parámetros pero conoce sus nombres, puede enviar los
argumentos en cualquier orden.
C#
Los argumentos con nombre también mejoran la legibilidad del código al identificar lo
que cada argumento representa. En el método de ejemplo siguiente, sellerName no
puede ser nulo ni un espacio en blanco. Como sellerName y productName son tipos de
cadena, en lugar de enviar argumentos por posición, tiene sentido usar argumentos con
nombre para eliminar la ambigüedad entre ambos y evitar confusiones para aquellos
que lean el código.
Los argumentos con nombre, cuando se usan con argumentos posicionales, son válidos
siempre que
C#
C#
Los argumentos posicionales que siguen a los argumentos con nombre desordenados
no son válidos.
C#
Ejemplo
Con este código se implementan los ejemplos de esta sección junto con otros
adicionales.
C#
class NamedExample
if (string.IsNullOrWhiteSpace(sellerName))
Argumentos opcionales
La definición de un método, constructor, indexador o delegado puede especificar que
sus parámetros son necesarios u opcionales. Todas las llamadas deben proporcionar
argumentos para todos los parámetros necesarios, pero pueden omitir los argumentos
para los parámetros opcionales.
C#
C#
//anExample.ExampleMethod(3, ,4);
Pero si conoce el nombre del tercer parámetro, puede usar un argumento con nombre
para realizar la tarea.
C#
7 Nota
Ejemplo
En el ejemplo siguiente, el constructor de ExampleClass tiene un solo parámetro, que es
opcional. El método de instancia ExampleMethod tiene un parámetro necesario, required ,
y dos parámetros opcionales, optionalstr y optionalint . El código de Main muestra las
distintas formas en que se pueden invocar el constructor y el método.
C#
namespace OptionalNamespace
class OptionalExample
// optional parameter.
anExample.ExampleMethod(2, "Two");
anExample.ExampleMethod(3);
// optional parameter.
anotherExample.ExampleMethod(2, "Two");
anotherExample.ExampleMethod(3);
// must be an integer.
//anExample.ExampleMethod("One", 1);
//anExample.ExampleMethod();
//anExample.ExampleMethod(3, ,4);
//anExample.ExampleMethod(3, 4);
// statement work.
class ExampleClass
_name = name;
Console.WriteLine(
En el código anterior se muestran varios ejemplos en los que los parámetros opcionales
no se aplican correctamente. En primer lugar, se muestra que se debe proporcionar un
argumento para el primer parámetro, que es obligatorio.
Interfaces COM
Los argumentos opcionales y con nombre, además de compatibilidad con objetos
dinámicos, mejoran considerablemente la interoperabilidad con las API de COM, como
las API de automatización de Office.
Por ejemplo, el método AutoFormat de la interfaz Range de Microsoft Office Excel tiene
siete parámetros, todos ellos opcionales. Estos parámetros se muestran en la ilustración
siguiente:
C#
excelApp.Workbooks.Add();
excelApp.Visible = true;
var myFormat =
Microsoft.Office.Interop.Excel.XlRangeAutoFormat.xlRangeAutoFormatAccounting
1;
Para obtener más información y ejemplos, vea Procedimiento para usar argumentos
opcionales y con nombre en la programación de Office (Guía de programación de C#) y
Procedimiento para tener acceso a objetos de interoperabilidad de Office mediante las
características de Visual C# (Guía de programación de C#).
Resolución de sobrecarga
El uso de argumentos opcionales y con nombre afecta a la resolución de sobrecarga de
las maneras siguientes:
) Importante
Debe tener Microsoft Office Word instalado en el equipo para completar estos
procedimientos.
7 Nota
C#
C#
static void DisplayInWord()
wordApp.Visible = true;
// open in Word.
Agregue el código siguiente al final del método para definir dónde se muestra texto en
el documento y qué texto se muestra:
C#
// is empty.
// current range.
Ejecución de la aplicación
Agregue la instrucción siguiente a Principal:
C#
DisplayInWord();
C#
// Convert to a simple table. The table will have a single row with
// three columns.
range.ConvertToTable(Separator: ",");
C#
C#
Format: Word.WdTableFormat.wdTableFormatElegant);
Ejemplo
En el código siguiente se incluye el ejemplo completo:
C#
using System;
namespace OfficeHowTo
class WordProgram
DisplayInWord();
wordApp.Visible = true;
// open in Word.
// is empty.
// current range.
// three columns.
range.ConvertToTable(Separator: ",");
Format: Word.WdTableFormat.wdTableFormatElegant);
Cada vez que se crea una instancia de una clase o un struct , se llama a su constructor.
Una clase o struct puede tener varios constructores que toman argumentos diferentes.
Los constructores permiten al programador establecer valores predeterminados, limitar
la creación de instancias y escribir código flexible y fácil de leer. Para obtener más
información y ejemplos, vea Constructores de instancias y Uso de constructores.
Hay varias acciones que forman parte de la inicialización de una nueva instancia. Estas
acciones tienen lugar en el orden siguiente:
Las acciones anteriores tienen lugar cuando se inicializa una nueva instancia. Si se
establece una nueva instancia de en struct su default valor, todos los campos de
instancia se establecen en 0.
last = lastName;
first = firstName;
Si un constructor puede implementarse como una instrucción única, puede usar una
definición del cuerpo de expresión. En el ejemplo siguiente se define una clase Location
cuyo constructor tiene un único parámetro de cadena denominado name. La definición
del cuerpo de expresión asigna el argumento al campo locationName .
C#
Constructores estáticos
En los ejemplos anteriores se han mostrado constructores de instancia, que crean un
objeto nuevo. Una clase o struct también puede tener un constructor estático, que
inicializa los miembros estáticos del tipo. Los constructores estáticos no tienen
parámetros. Si no proporciona un constructor estático para inicializar los campos
estáticos, el compilador de C# inicializa los campos estáticos en su valor
predeterminado, tal como se muestra en el artículo Valores predeterminados de los
tipos de C#.
En el ejemplo siguiente se usa un constructor estático para inicializar un campo estático.
C#
{ }
static Adult()
minimumAge = 18;
C#
{ }
En esta sección
Utilizar constructores
Constructores de instancias
Constructores privados
Constructores estáticos
Escritura de un constructor de copia
Vea también
Guía de programación de C#
El sistema de tipos de C#
Finalizadores
static
Why Do Initializers Run In The Opposite Order As Constructors? Part One (¿Por qué
los inicializadores se ejecutan en orden contrario a los constructores? Parte uno)
Utilizar constructores (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 4 minutos
Cuando se crea una instancia de una class o un struct, se llama a su constructor. Los
constructores tienen el mismo nombre que la class o el struct y suelen inicializar los
miembros de datos del nuevo objeto.
C#
public Taxi()
IsInitialized = true;
class TestTaxi
Console.WriteLine(t.IsInitialized);
A menos que la clase sea static, las clases sin constructores tienen un constructor
público sin parámetros por el compilador de C# con el fin de habilitar la creación de
instancias de clase. Para más información, vea Clases estáticas y sus miembros.
Puede impedir que se cree una instancia de una clase convirtiendo el constructor en
privado, de la manera siguiente:
C#
class NLog
// Private Constructor:
private NLog() { }
Para obtener más información, vea Private Constructors (Constructores privados [Guía
de programación de C#]).
Los constructores de tipos struct son similares a los constructores de clases, pero los
structs no pueden contener un constructor sin parámetros explícito porque el
compilador proporciona uno automáticamente. Este constructor inicializa cada campo
del struct en los valores predeterminados. Pero este constructor sin parámetros solo se
invoca si las instancias de struct se crean con new . Por ejemplo, este código usa el
constructor sin parámetros para Int32, por lo que se tiene la certeza de que el entero se
inicializa:
C#
Console.WriteLine(i);
7 Nota
Sin embargo, el siguiente código genera un error del compilador porque no usa new y
porque intenta usar un objeto que no se ha inicializado:
C#
int i;
Console.WriteLine(i);
También puede inicializar o asignar los objetos basados en structs (incluidos todos los
tipos numéricos integrados) y luego usarlos como en el ejemplo siguiente:
C#
int b;
Así que no es necesario llamar al constructor sin parámetros para un tipo de valor.
Tanto las clases como los structs pueden definir constructores que toman parámetros.
Los constructores que toman parámetros deben llamarse mediante una instrucción new
o base. Las clases y structs también pueden definir varios constructores y no es
necesario definir un constructor sin parámetros. Por ejemplo:
C#
public Employee() { }
Salary = annualSalary;
C#
Un constructor puede usar la palabra clave base para llamar al constructor de una clase
base. Por ejemplo:
C#
public class Manager : Employee
: base(annualSalary)
En este ejemplo, se llama al constructor de la clase base antes de ejecutar el bloque del
constructor. La palabra clave base puede usarse con o sin parámetros. Los parámetros
del constructor se pueden usar como parámetros en base o como parte de una
expresión. Para obtener más información, vea base.
C#
C#
: base()
Si una clase base no proporciona un constructor sin parámetros, la clase derivada debe
realizar una llamada explícita a un constructor base mediante base .
C#
public Employee(int weeklySalary, int numberOfWeeks)
: this(weeklySalary * numberOfWeeks)
C#
Salary = annualSalary;
Los constructores se pueden marcar como public, private, protected, internal, protected
internal o private protected. Estos modificadores de acceso definen cómo los usuarios
de la clase pueden construir la clase. Para obtener más información, consulte
Modificadores de acceso.
Consulte también
Guía de programación de C#
El sistema de tipos de C#
Constructores
Finalizadores
Constructores de instancias (guía de
programación de C#)
Artículo • 10/02/2023 • Tiempo de lectura: 2 minutos
C#
class Coords
public Coords()
: this(0, 0)
{ }
X = x;
Y = y;
class Example
Console.WriteLine($"Coords #1 at {p1}");
Console.WriteLine($"Coords #2 at {p2}");
C#
protected double x, y;
this.x = x;
this.y = y;
: base(radius, 0)
{ }
: base(radius)
y = height;
class Example
C#
class Example
Ese constructor inicializa los campos de instancia y las propiedades según los
inicializadores correspondientes. Si un campo o una propiedad no tiene ningún
inicializador, su valor se establece en el valor predeterminado del tipo de la propiedad o
del campo. Si declara al menos un constructor de instancia en una clase, C# no
proporciona un constructor sin parámetros.
C#
class NLog
// Private Constructor:
private NLog() { }
Los constructores privados se usan para evitar la creación de instancias de una clase
cuando no hay campos o métodos de instancia, por ejemplo, la clase Math, o cuando se
llama a un método para obtener una instancia de una clase. Si todos los métodos de la
clase son estáticos, considere convertir la clase completa en estática. Para obtener más
información, vea Clases estáticas y sus miembros.
Ejemplo
El siguiente es un ejemplo de clase que usa un constructor privado.
C#
private Counter() { }
return ++currentCount;
class TestCounter
Counter.currentCount = 100;
Counter.IncrementCount();
Console.WriteLine("New count: {0}", Counter.currentCount);
Console.ReadKey();
C#
Vea también
Guía de programación de C#
El sistema de tipos de C#
Constructores
Finalizadores
private
public
Constructores estáticos (Guía de
programación de C#)
Artículo • 31/01/2023 • Tiempo de lectura: 5 minutos
Un constructor estático se usa para inicializar cualquier dato estático o realizar una
acción determinada que solo debe realizarse una vez. Es llamado automáticamente
antes de crear la primera instancia o de hacer referencia a cualquier miembro estático.
Se llamará a un constructor estático como máximo una vez.
C#
class SimpleClass
static SimpleClass()
baseline = DateTime.Now.Ticks;
Hay varias acciones que forman parte de la inicialización estática. Estas acciones tienen
lugar en el orden siguiente:
Comentarios
Los constructores estáticos tienen las propiedades siguientes:
7 Nota
Aunque no es directamente accesible, la presencia de un constructor estático
explícito debe documentarse para ayudar con la solución de problemas de
excepciones de inicialización.
Uso
Los constructores estáticos se usan normalmente cuando la clase hace uso de un
archivo de registro y el constructor escribe entradas en dicho archivo.
Los constructores estáticos también son útiles al crear clases contenedoras para
código no administrado, cuando el constructor puede llamar al método
LoadLibrary .
Ejemplo
En este ejemplo, la clase Bus tiene un constructor estático. Cuando se crea la primera
instancia de Bus ( bus1 ), se invoca el constructor estático para inicializar la clase. En el
resultado del ejemplo, se comprueba que el constructor estático se ejecuta solo una vez,
incluso si se crean dos instancias de Bus , y que se ejecuta antes de que se ejecute el
constructor de instancia.
C#
// Represents the time the first bus of the day starts its route.
globalStartTime = DateTime.Now;
globalStartTime.ToLongTimeString());
// Instance constructor.
RouteNumber = routeNum;
// Instance method.
this.RouteNumber,
elapsedTime.Milliseconds,
globalStartTime.ToShortTimeString());
class TestBus
bus1.Drive();
System.Threading.Thread.Sleep(25);
bus2.Drive();
Console.ReadKey();
/* Sample output:
71 is starting its route 6.00 minutes after global start time 3:57 PM.
72 is starting its route 31.00 minutes after global start time 3:57 PM.
*/
Vea también
Guía de programación de C#
El sistema de tipos de C#
Constructores
Clases estáticas y sus miembros
Finalizadores
Instrucciones de diseño de constructores
Advertencia de seguridad - CA2121: Los constructores estáticos deben ser
privados
Inicializadores de módulo
Procedimiento para escribir un
constructor de copia (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Ejemplo
En el ejemplo siguiente, Person class define un constructor de copias que toma, como
argumento, una instancia de Person . Los valores de las propiedades de los argumentos
se asignan a las propiedades de la nueva instancia de Person . El código contiene un
constructor de copias alternativo que envía las propiedades Name y Age de la instancia
que quiere copiar al constructor de instancia de la clase.
C#
class Person
// Copy constructor.
Name = previousPerson.Name;
Age = previousPerson.Age;
}
// : this(previousPerson.Name, previousPerson.Age)
//{
//}
// Instance constructor.
Name = name;
Age = age;
class TestPerson
person1.Age = 39;
person2.Age = 41;
// Show details to verify that the name and age fields are distinct.
Console.WriteLine(person1.Details());
Console.WriteLine(person2.Details());
Console.ReadKey();
// Output:
// George is 39
// Charles is 41
Vea también
ICloneable
Registros
Guía de programación de C#
El sistema de tipos de C#
Constructores
Finalizadores
Finalizadores (Guía de programación de
C#)
Artículo • 28/11/2022 • Tiempo de lectura: 5 minutos
Comentarios
Los finalizadores no se pueden definir en structs. Solo se usan con clases.
Una clase solo puede tener un finalizador.
Los finalizadores no se pueden heredar ni sobrecargar.
No se puede llamar a los finalizadores. Se invocan automáticamente.
Un finalizador no permite modificadores ni tiene parámetros.
Por ejemplo, el siguiente código muestra una declaración de un finalizador para la clase
Car .
C#
class Car
~Car() // finalizer
// cleanup statements...
C#
El finalizador llama implícitamente a Finalize en la clase base del objeto. Por lo tanto,
una llamada a un finalizador se convierte implícitamente al siguiente código:
C#
try
// Cleanup statements...
finally
base.Finalize();
Este diseño significa que se realizan llamadas al método Finalize de manera recursiva
para todas las instancias de la cadena de herencia, desde la más a la menos derivada.
7 Nota
Los finalizadores vacíos no deben usarse. Cuando una clase contiene un finalizador,
se crea una entrada en la cola Finalize . El recolector de elementos no utilizados
procesa esta cola. Cuando esto sucede, llama a cada finalizador. Los finalizadores
innecesarios, como los vacíos, los que solo llaman al finalizador de clase base o los
que solo llaman a métodos emitidos condicionalmente provocan una pérdida
innecesaria de rendimiento.
7 Nota
Si necesita realizar la limpieza de forma confiable cuando existe una aplicación, registre
un controlador para el evento System.AppDomain.ProcessExit. Ese controlador
garantizaría la llamada a IDisposable.Dispose(), o a IAsyncDisposable.DisposeAsync(),
para todos los objetos que requieren limpieza antes de que la aplicación se cierre. Dado
que no se puede llamar directamente a Finalizar y no se puede garantizar que el
recolector de elementos no utilizados llame a todos los finalizadores antes de salir, debe
usar Dispose o DisposeAsync para asegurarse de que se liberan los recursos.
Para obtener más información sobre la limpieza de recursos, vea los siguientes artículos:
Ejemplo
En el siguiente ejemplo se crean tres clases que forman una cadena de herencia. La clase
First es la clase base, Second se deriva de First y Third se deriva de Second . Los tres
tienen finalizadores. En Main , se crea una instancia de la clase más derivada. La salida de
este código depende de la implementación de .NET a la que se dirige la aplicación:
C#
class First
~First()
~Second()
~Third()
/*
t = null;
*/
Consulte también
IDisposable
Guía de programación de C#
Constructores
Recolección de elementos no utilizados
Inicializadores de objeto y de colección
(Guía de programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 10 minutos
Inicializadores de objeto
Los inicializadores de objeto permiten asignar valores a cualquier campo o propiedad
accesible de un objeto en el momento de su creación sin tener que invocar un
constructor seguido de líneas de instrucciones de asignación. La sintaxis de inicializador
de objetos permite especificar argumentos para un constructor u omitir los argumentos
(y la sintaxis de paréntesis). En el ejemplo siguiente se muestra cómo usar un
inicializador de objeto con un tipo con nombre, Cat , y cómo invocar el constructor sin
parámetros. Tenga en cuenta el uso de propiedades implementadas automáticamente
en la clase Cat . Para obtener más información, vea Propiedades implementadas
automáticamente.
C#
// Auto-implemented properties.
public Cat()
this.Name = name;
C#
C#
C#
[0, 0] = 1.0,
[0, 1] = 0.0,
[0, 2] = 0.0,
[1, 0] = 0.0,
[1, 1] = 1.0,
[1, 2] = 0.0,
[2, 0] = 0.0,
[2, 1] = 0.0,
[2, 2] = 1.0,
};
C#
var thing = new IndexersExample {
[1] = '1',
[2] = '4',
[3] = '9',
Size = Math.PI,
Para que el código anterior se compile, el tipo IndexersExample debe tener los
siguientes miembros:
C#
C#
Los tipos anónimos permiten a la cláusula select de una expresión de consulta LINQ
transformar objetos de la secuencia original en objetos cuyo valor y forma pueden ser
distintos de los originales. Esto resulta útil si desea almacenar solo una parte de la
información de cada objeto en una secuencia. En el ejemplo siguiente, suponga que un
objeto del producto ( p ) contiene numerosos campos y métodos y que solo le interesa
crear una secuencia de objetos que contenga el nombre del producto y el precio por
unidad.
C#
var productInfos =
from p in products
C#
foreach(var p in productInfos){...}
Cada objeto del nuevo tipo anónimo tiene dos propiedades públicas que reciben los
mismos nombres que las propiedades o los campos del objeto original. También puede
cambiar el nombre de un campo al crear un tipo anónimo; en el ejemplo siguiente se
cambia el nombre del campo UnitPrice a Price .
C#
Inicializadores de colección
Los inicializadores de colección le permiten especificar uno o varios inicializadores de
elemento al inicializar un tipo de colección que implementa IEnumerable y tiene Add
con la firma apropiada como un método de instancia o un método de extensión. Los
inicializadores de elemento pueden ser un valor simple, una expresión o un inicializador
de objeto. Si se usa un inicializador de colección, no es necesario especificar varias
llamadas; el compilador las agrega automáticamente.
C#
C#
};
C#
null
};
C#
[7] = "seven",
[9] = "nine",
[13] = "thirteen"
};
El ejemplo anterior genera código que llama a Item[TKey] para establecer los valores.
Puede inicializar también diccionarios y otros contenedores asociativos con la sintaxis
siguiente. Tenga en cuenta que en lugar de sintaxis de indizador, con paréntesis y una
asignación, usa un objeto con varios valores:
C#
{19, "nineteen" },
{23, "twenty-three" },
{42, "forty-two" }
};
Este ejemplo de inicializador llama a Add(TKey, TValue) para agregar los tres elementos
al diccionario. Estas dos maneras distintas de inicializar colecciones asociativas tienen un
comportamiento ligeramente diferente debido a las llamadas a métodos que genera el
compilador. Ambas variantes funcionan con la clase Dictionary . Es posible que otros
tipos solo admitan una o la otra, en función de su API pública.
Inicializadores de objeto con inicialización de
propiedades de solo lectura de colección
Algunas clases pueden tener propiedades de colección donde la propiedad es de solo
lectura, como la propiedad Cats de CatOwner en el caso siguiente:
C#
No podrá usar la sintaxis del inicializador de colección abordada hasta ahora, ya que no
se puede asignar una nueva lista a la propiedad:
C#
};
C#
Cats =
};
Ejemplos
En el ejemplo siguiente se combinan los conceptos de inicializadores de objeto y
colección.
C#
// Auto-implemented properties.
public Cat() { }
Name = name;
};
null
};
// Display results.
System.Console.WriteLine(cat.Name);
System.Console.WriteLine(c.Name);
if (c != null)
System.Console.WriteLine(c.Name);
else
// Output:
//Fluffy
//Sylvester
//Whiskers
//Sasha
//Furrytail
//Peaches
C#
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator() =>
internalList.GetEnumerator();
$@"{firstname} {lastname}
{street}
);
};
Console.WriteLine("Address Entries:");
Console.WriteLine("\r\n" + addressEntry);
/*
* Prints:
Address Entries:
John Doe
123 Street
Topeka, KS 00000
Jane Smith
456 Street
Topeka, KS 00000
*/
Los métodos Add pueden usar la palabra clave params para tomar un número variable
de argumentos, como se muestra en el ejemplo siguiente. En este ejemplo además se
muestra la implementación personalizada de un indizador para inicializar una colección
mediante índices.
C#
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator() =>
internalDictionary.GetEnumerator();
storedValues.AddRange(values);
RudimentaryMultiValuedDictionary<string, string>
rudimentaryMultiValuedDictionary1
= new RudimentaryMultiValuedDictionary<string, string>()
};
RudimentaryMultiValuedDictionary<string, string>
rudimentaryMultiValuedDictionary2
= new RudimentaryMultiValuedDictionary<string, string>()
};
RudimentaryMultiValuedDictionary<string, string>
rudimentaryMultiValuedDictionary3
= new RudimentaryMultiValuedDictionary<string, string>()
};
Console.WriteLine(member);
Console.WriteLine(member);
Console.WriteLine(member);
/*
* Prints:
Bob
John
Mary
Eric
Emily
Debbie
Jesse
Bob
John
Mary
Eric
Emily
Debbie
Jesse
Bob
John
Mary
Eric
Emily
Debbie
Jesse
*/
Vea también
Uso de inicializadores de objetos (regla de estilo IDE0017)
Uso de inicializadores de colección (regla de estilo IDE0028)
Guía de programación de C#
LINQ en C#
Tipos anónimos
Procedimiento para inicializar objetos
usando un inicializador de objeto (Guía
de programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
Puede usar inicializadores de objeto para inicializar objetos de tipo de una forma
declarativa sin tener que invocar explícitamente un constructor para el tipo.
En los siguientes ejemplos se muestra cómo usar los inicializadores de objeto con
objetos con nombre. El compilador procesa los inicializadores de objeto primero
obteniendo acceso al constructor de instancia sin parámetros y después procesando las
inicializaciones de miembro. Por lo tanto, si el constructor sin parámetros se declara
como private en la clase, se producirá un error en los inicializadores de objeto que
requieren acceso público.
Debe usar un inicializador de objeto si va a definir un tipo anónimo. Para obtener más
información, vea Procedimiento para devolver subconjuntos de propiedades de
elementos en una consulta.
Ejemplo
En el siguiente ejemplo se muestra cómo inicializar un nuevo tipo StudentName usando
inicializadores de objeto. Este ejemplo establece propiedades en el tipo StudentName :
C#
// two parameters.
FirstName = "Craig",
LastName = "Playstead"
};
// initializers.
ID = 183
};
FirstName = "Craig",
LastName = "Playstead",
ID = 116
};
Console.WriteLine(student1.ToString());
Console.WriteLine(student2.ToString());
Console.WriteLine(student3.ToString());
Console.WriteLine(student4.ToString());
// Output:
// Craig 0
// Craig 0
// 183
// Craig 116
// You can test this by changing the access modifier from public to
// fail.
public StudentName() { }
// properties.
FirstName = first;
LastName = last;
// Properties.
C#
};
};
Console.WriteLine(team["2B"]);
Vea también
Guía de programación de C#
Inicializadores de objeto y colección
Procedimientos: inicialización de un
diccionario con un inicializador de
colección (guía de programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Ejemplo
En el ejemplo de código siguiente, Dictionary<TKey,TValue> se inicializa con instancias
de tipo StudentName . La primera inicialización usa el método Add con dos argumentos.
El compilador genera una llamada a Add por cada uno de los pares de claves int y
valores StudentName . La segunda usa un método de indizador de lectura y escritura
público de la clase Dictionary :
C#
class StudentName
};
Console.WriteLine($"Student {index} is
{students[index].FirstName} {students[index].LastName}");
Console.WriteLine();
};
Console.WriteLine($"Student {index} is
{students2[index].FirstName} {students2[index].LastName}");
Vea también
Guía de programación de C#
Inicializadores de objeto y colección
Tipos anidados (Guía de programación
de C#)
Artículo • 04/01/2023 • Tiempo de lectura: 2 minutos
Un tipo definido en una clase, estructura o interfaz se denomina tipo anidado. Por
ejemplo
C#
class Nested
Nested() { }
Con independencia de si el tipo externo es una clase, una interfaz o una estructura, los
tipos anidados se establecen de manera predeterminada en private; solo son accesibles
desde su tipo contenedor. En el ejemplo anterior, la clase Nested es inaccesible a los
tipos externos.
Los tipos anidados de una clase pueden ser public, protected, internal, protected
internal, private o private protected.
C#
Nested() { }
El tipo anidado o interno puede tener acceso al tipo contenedor o externo. Para tener
acceso al tipo contenedor, páselo como un argumento al constructor del tipo anidado.
Por ejemplo:
C#
public Nested()
this.parent = parent;
Un tipo anidado tiene acceso a todos los miembros que estén accesibles para el tipo
contenedor. Puede tener acceso a los miembros privados y protegidos del tipo
contenedor, incluidos los miembros protegidos heredados.
C#
Vea también
Guía de programación de C#
El sistema de tipos de C#
Modificadores de acceso
Constructores
Regla CA1034
Clases y métodos parciales (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 7 minutos
Es posible dividir la definición de una clase, un struct, una interfaz o un método en dos o
más archivos de código fuente. Cada archivo de código fuente contiene una sección de
la definición de tipo o método, y todos los elementos se combinan cuando se compila la
aplicación.
Clases parciales
Es recomendable dividir una definición de clase en varias situaciones:
Cuando se trabaja con proyectos grandes, el hecho de repartir una clase entre
archivos independientes permite que varios programadores trabajen en ella al
mismo tiempo.
Cuando se trabaja con código fuente generado automáticamente, se puede
agregar código a la clase sin tener que volver a crear el archivo de código fuente.
Visual Studio usa este enfoque al crear formularios Windows Forms, código de
contenedor de servicio Web, etc. Puede crear código que use estas clases sin
necesidad de modificar el archivo creado por Visual Studio.
Al usar generadores de código fuente para generar funcionalidades adicionales en
una clase.
Para dividir una definición de clase, use el modificador de palabra clave partial, como se
muestra aquí:
C#
La palabra clave partial indica que se pueden definir en el espacio de nombres otros
elementos de la clase, la estructura o la interfaz. Todos los elementos deben usar la
palabra clave partial . Todos los elementos deben estar disponibles en tiempo de
compilación para formar el tipo final. Todos los elementos deben tener la misma
accesibilidad, como public , private , etc.
Todos los elementos que especifiquen una clase base deben coincidir, pero los
elementos que omitan una clase base heredan igualmente el tipo base. Los elementos
pueden especificar diferentes interfaces base, y el tipo final implementa todas las
interfaces enumeradas por todas las declaraciones parciales. Todas las clases, structs o
miembros de interfaz declarados en una definición parcial están disponibles para todos
los demás elementos. El tipo final es la combinación de todos los elementos en tiempo
de compilación.
7 Nota
En el ejemplo siguiente se muestra que los tipos anidados pueden ser parciales, incluso
si el tipo en el que están anidados no es parcial.
C#
class Container
void Test() { }
void Test2() { }
[SerializableAttribute]
[ObsoleteAttribute]
C#
[SerializableAttribute]
[ObsoleteAttribute]
class Moon { }
comentarios XML
interfaces
atributos de parámetro de tipo genérico
class (atributos)
miembros
C#
C#
Restricciones
Debe seguir varias reglas al trabajar con definiciones de clase parcial:
Todas las definiciones de tipo parcial que van a formar parte del mismo tipo deben
modificarse con partial . Por ejemplo, las declaraciones de clase siguientes
generan un error:
C#
C#
Todas las definiciones de tipo parcial que van a formar parte del mismo tipo deben
definirse en el mismo ensamblado y en el mismo módulo (archivo .exe o .dll). Las
definiciones parciales no pueden abarcar varios módulos.
El nombre de clase y los parámetros de tipo genérico deben coincidir en todas las
definiciones de tipo parcial. Los tipos genéricos pueden ser parciales. Cada
declaración parcial debe usar los mismos nombres de parámetro en el mismo
orden.
Las siguientes palabras clave son opcionales en una definición de tipo parcial, pero
si están presentes la definición, no pueden entrar en conflicto con las palabras
clave especificadas en otra definición parcial para el mismo tipo:
public
private
protected
internal
abstract
sealed
clase base
modificador new (elementos anidados)
restricciones genéricas
C#
private int x;
private int y;
this.x = x;
this.y = y;
class TestCoords
myCoords.PrintCoords();
Console.ReadKey();
C#
void Interface_Test();
void Interface_Test2();
partial struct S1
void Struct_Test() { }
partial struct S1
void Struct_Test2() { }
Métodos Partial
Una clase o struct parcial puede contener un método parcial. Un elemento de la clase
contiene la firma del método. Una implementación se puede definir en el mismo
elemento o en otro. Si no se proporciona la implementación, el método y todas las
llamadas al método se quitan en tiempo de compilación. La implementación puede ser
necesaria en función de la signatura del método. No es necesario que un método parcial
tenga una implementación en los casos siguientes:
Cualquier método que no cumpla todas estas restricciones (por ejemplo, public virtual
partial void ) debe proporcionar una implementación. Esa implementación la puede
proporcionar un generador de código fuente.
Los métodos parciales permiten que el implementador de una parte de una clase
declare un método. El implementador de otra parte de la clase puede definir ese
método. Hay dos escenarios en los que esto es útil: plantillas que generan código
reutilizable y generadores de código fuente.
C#
// Definition in file1.cs
// Implementation in file2.cs
// method body
Use un tipo anónimo en una expresión de consulta cuando se cumplan estas dos
condiciones:
Si solo quiere devolver una propiedad o campo de cada elemento de origen, puede usar
simplemente el operador de punto en la cláusula select . Por ejemplo, para devolver
solo el ID de cada student , escriba la cláusula select como sigue:
C#
select student.ID;
Ejemplo
En el ejemplo siguiente se muestra cómo usar un tipo anónimo para devolver solo un
subconjunto de las propiedades de cada elemento de origen que coincida con la
condición especificada.
C#
var queryHighScores =
/* Output:
Adams, Terry
Fakhouri, Fadi
Garcia, Cesar
Omelchenko, Svetlana
Zabokritski, Eugene
*/
Tenga en cuenta que, si no se especifica ningún nombre, el tipo anónimo usa los
nombres del elemento de origen para sus propiedades. Para asignar nombres nuevos a
las propiedades del tipo anónimo, escriba la instrucción select como sigue:
C#
C#
Compilar el código
Para ejecutar este código, copie y pegue la clase en una aplicación de consola de C#
con una directiva using de System.Linq.
Consulte también
Guía de programación de C#
Tipos anónimos
LINQ en C#
Implementación de interfaz explícita
(Guía de programación de C#)
Artículo • 11/02/2023 • Tiempo de lectura: 3 minutos
Si una clase implementa dos interfaces que contienen un miembro con la misma firma,
entonces al implementar ese miembro en la clase ambas interfaces usarán ese miembro
como su implementación. En el ejemplo siguiente, todas las llamadas a Paint invocan el
mismo método. En este primer ejemplo se definen los tipos:
C#
void Paint();
void Paint();
C#
sample.Paint();
control.Paint();
surface.Paint();
// Output:
Pero es posible que no quiera que se llame a la misma implementación para las dos
interfaces. Para llamar a otra implementación en función de la interfaz en uso, puede
implementar un miembro de interfaz de forma explícita. Una implementación de interfaz
explícita es un miembro de clase al que solo se llama a través de la interfaz especificada.
Asigne al miembro de clase el nombre de la interfaz y un punto como prefijo. Por
ejemplo:
C#
void IControl.Paint()
System.Console.WriteLine("IControl.Paint");
void ISurface.Paint()
System.Console.WriteLine("ISurface.Paint");
C#
// Output:
// IControl.Paint
// ISurface.Paint
La implementación explícita también se usa para resolver casos donde dos interfaces
declaran miembros diferentes del mismo nombre como una propiedad y un método.
Para implementar ambas interfaces, una clase tiene que usar la implementación explícita
para la propiedad P o el método P , o ambos, para evitar un error del compilador. Por
ejemplo:
C#
interface ILeft
int P { get;}
interface IRight
int P();
Puede definir una implementación para los miembros declarados en una interfaz. Si una
clase hereda una implementación de método de una interfaz, ese método solo es
accesible a través de una referencia del tipo de interfaz. El miembro heredado no
aparece como parte de la interfaz pública. En el ejemplo siguiente se define una
implementación predeterminada para un método de interfaz:
C#
C#
control.Paint();
Cualquier clase que implemente la interfaz IControl puede invalidar el método Paint
predeterminado, ya sea como un método público, o bien como una implementación de
interfaz explícita.
Consulte también
Guía de programación de C#
Programación orientada a objetos
Interfaces
Herencia
Procedimiento Implementar
explícitamente miembros de interfaz
(Guía de programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Este ejemplo declara una interfaz, IDimensions , y una clase, Box , que implementa
explícitamente los miembros de interfaz GetLength y GetWidth . Se tiene acceso a los
miembros mediante la instancia de interfaz dimensions .
Ejemplo
C#
interface IDimensions
float GetLength();
float GetWidth();
float lengthInches;
float widthInches;
lengthInches = length;
widthInches = width;
float IDimensions.GetLength()
{
return lengthInches;
float IDimensions.GetWidth()
return widthInches;
/* Output:
Length: 30
Width: 20
*/
Programación sólida
Tenga en cuenta que las siguientes líneas, en el método Main , se comentan porque
producirían errores de compilación. No se puede tener acceso a un miembro de
interfaz que se implementa explícitamente desde una instancia class:
C#
Tenga en cuenta también que las líneas siguientes, en el método Main , imprimen
correctamente las dimensiones del cuadro porque se llama a los métodos desde
una instancia de la interfaz:
C#
Consulte también
Guía de programación de C#
Programación orientada a objetos
Interfaces
Procedimiento para implementar miembros de dos interfaces de forma explícita
Procedimiento Implementar
explícitamente miembros de dos
interfaces (Guía de programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Ejemplo
C#
interface IEnglishDimensions
float Length();
float Width();
interface IMetricDimensions
float Length();
float Width();
float lengthInches;
float widthInches;
this.lengthInches = lengthInches;
this.widthInches = widthInches;
/* Output:
Length(in): 30
Width (in): 20
Length(cm): 76.2
*/
Programación sólida
Si quiere realizar las medidas en unidades inglesas de manera predeterminada,
implemente los métodos Length y Width con normalidad e implemente explícitamente
los métodos Length y Width de la interfaz IMetricDimensions:
C#
// Normal implementation:
// Explicit implementation:
En este caso, se puede tener acceso a las unidades inglesas desde la instancia de clase y
acceso a las unidades métricas desde la instancia de interfaz:
C#
Consulte también
Guía de programación de C#
Programación orientada a objetos
Interfaces
Procedimiento para implementar miembros de interfaz de forma explícita
Delegados (Guía de programación de
C#)
Artículo • 10/02/2023 • Tiempo de lectura: 3 minutos
Los delegados se utilizan para pasar métodos como argumentos a otros métodos. Los
controladores de eventos no son más que métodos que se invocan a través de
delegados. Cree un método personalizado y una clase, como un control de Windows,
podrá llamar al método cuando se produzca un determinado evento. En el siguiente
ejemplo se muestra una declaración de delegado:
C#
Cualquier método de cualquier clase o struct accesible que coincida con el tipo de
delegado se puede asignar al delegado. El método puede ser estático o de instancia.
Esta flexibilidad significa que puede cambiar las llamadas de método mediante
programación, o bien agregar código nuevo a las clases existentes.
7 Nota
Esta capacidad de hacer referencia a un método como parámetro hace que los
delegados sean idóneos para definir métodos de devolución de llamada. Puede escribir
un método que compare dos objetos en la aplicación. Ese método se puede usar en un
delegado para un algoritmo de ordenación. Como el código de comparación es
independiente de la biblioteca, el método de ordenación puede ser más general.
Los delegados son similares a los punteros de función de C++, pero los primeros
están completamente orientados a objetos y, a diferencia de los punteros de C++
de funciones de miembro, los delegados encapsulan una instancia de objeto y un
método.
Los delegados permiten pasar los métodos como parámetros.
Los delegados pueden usarse para definir métodos de devolución de llamada.
Los delegados pueden encadenarse entre sí; por ejemplo, se puede llamar a varios
métodos en un solo evento.
No es necesario que los métodos coincidan exactamente con el tipo de delegado.
Para obtener más información, consulte Usar varianza en delegados.
Las expresiones lambda son una manera más concisa de escribir bloques de
código alineado. En determinados contextos, las expresiones lambda se compilan
en tipos de delegado. Para más información sobre las expresiones lambda,
consulte Expresiones lambda.
En esta sección
Utilizar delegados
Cuándo usar delegados en lugar de interfaces (Guía de programación de C#)
Delegados con métodos con nombre y delegados con métodos anónimos
Uso de varianza en delegados
Procedimiento para combinar delegados (delegados de multidifusión)
Procedimiento para declarar un delegado, crear instancias del mismo y usarlo
Consulte también
Delegate
Guía de programación de C#
Eventos
Utilizar delegados (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 5 minutos
C#
C#
Console.WriteLine(message);
C#
handler("Hello World");
Los tipos de delegado se derivan de la clase Delegate en .NET. Los tipos de delegados
son sealed (no se pueden derivar) y no se pueden derivar clases personalizadas de
Delegate. Dado que el delegado con instancias es un objeto, puede pasarse como
argumento o asignarse a una propiedad. De este modo, un método puede aceptar un
delegado como parámetro y llamar al delegado en algún momento posterior. Esto se
conoce como devolución de llamada asincrónica y es un método común para notificar a
un llamador que un proceso largo ha finalizado. Cuando se utiliza un delegado de esta
manera, el código que usa al delegado no necesita ningún conocimiento de la
implementación del método empleado. La funcionalidad es similar a la encapsulación
que proporcionan las interfaces.
C#
C#
MethodWithCallback(1, 2, handler);
Consola
C#
Un delegado puede llamar a más de un método cuando se invoca. Esto se conoce como
multidifusión. Para agregar un método adicional a la lista de métodos del delegado —la
lista de invocación—, simplemente es necesario agregar dos delegados mediante los
operadores de adición o asignación y suma ('+' o '+='). Por ejemplo:
C#
Del d1 = obj.Method1;
Del d2 = obj.Method2;
Del d3 = DelegateMethod;
//remove Method1
allMethodsDelegate -= d1;
C#
C#
// Compile-time error.
//Console.WriteLine(d == e);
Console.WriteLine(d == f);
Consulte también
Guía de programación de C#
Delegados
Uso de varianza en delegados
Varianza en delegados
Uso de varianza para los delegados genéricos Func y Action
Eventos
Delegados con métodos con nombre y
Métodos anónimos (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Un delegado puede asociarse con un método con nombre. Cuando crea una instancia
de un delegado mediante un método con nombre, el método se pasa como un
parámetro, por ejemplo:
C#
// Declare a delegate.
Del d = obj.DoWork;
Esto se llama con un método con nombre. Los delegados construidos con un método
con nombre pueden encapsular un método estático o un método de instancia. Los
métodos con nombre son la única manera de crear una instancia de un delegado en
versiones anteriores de C#. En cambio, en una situación en la que crear un método
nuevo es una sobrecarga no deseada, C# le permite crear una instancia de un delegado
y especificar inmediatamente un bloque de código que el delegado procesará cuando
se llame. El bloque puede contener una expresión lambda o un método anónimo.
El método que pasa como un parámetro de delegado debe tener la misma firma que la
declaración de delegado. Una instancia de delegado puede encapsular un método de
instancia o estático.
7 Nota
A partir de C# 10, los grupos de métodos con una única sobrecarga tienen un tipo
natural. Esto significa que el compilador puede deducir el tipo de valor devuelto y los
tipos de parámetro para el tipo delegado:
C#
Ejemplos
A continuación se muestra un ejemplo sencillo de cómo declarar y usar un delegado.
Tenga en cuenta que tanto el delegado, Del , como el método asociado,
MultiplyNumbers , tienen la misma firma
C#
// Declare a delegate
class MathClass
Del d = m.MultiplyNumbers;
d(i, 2);
Console.ReadKey();
/* Output:
2 4 6 8 10
*/
C#
// Declare a delegate
class SampleClass
class TestSampleClass
Del d = sc.InstanceMethod;
d();
d = SampleClass.StaticMethod;
d();
/* Output:
*/
Consulte también
Guía de programación de C#
Delegados
Procedimiento para combinar delegados (delegados de multidifusión)
Eventos
Procedimiento para combinar
delegados (delegados de multidifusión)
(Guía de programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
En este ejemplo se muestra cómo crear delegados de multidifusión. Una propiedad útil
de los objetos delegados es que puedan asignarse objetos múltiples a una instancia de
delegado con el operador + . El delegado de multidifusión contiene una lista de los
delegados asignados. Cuando se llama al delegado de multidifusión, invoca a los
delegados de la lista, en orden. Solo los delegados del mismo tipo pueden combinarse.
Ejemplo
C#
using System;
// Define a custom delegate that has a string parameter and returns void.
class TestClass
// method Hello.
hiDel = Hello;
// method Goodbye.
byeDel = Goodbye;
// form multiDel.
hiDel("A");
byeDel("B");
multiDel("C");
multiMinusHiDel("D");
/* Output:
Hello, A!
Goodbye, B!
Hello, C!
Goodbye, C!
Goodbye, D!
*/
Consulte también
MulticastDelegate
Guía de programación de C#
Eventos
Procedimiento Declarar un delegado,
crear instancias del mismo y utilizarlo
(Guía de programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 4 minutos
C#
// Declare a delegate.
C#
C#
C#
C#
// Instantiate Del by using a lambda expression.
En el ejemplo siguiente se ilustra cómo declarar un delegado, crear una instancia del
mismo y utilizarlo La clase BookDB encapsula una base de datos de una librería que
mantiene una base de datos de libros. Expone un método ProcessPaperbackBooks , que
busca todos los libros de bolsillo en la base de datos y llama a un delegado para cada
uno. El tipo delegate utilizado se denomina ProcessBookCallback . La clase Test utiliza
esta clase para imprimir los títulos y el precio medio de los libros de bolsillo.
Ejemplo
C#
namespace Bookstore
using System.Collections;
Title = title;
Author = author;
Price = price;
Paperback = paperBack;
if (b.Paperback)
processBook(b);
namespace BookTestClient
using Bookstore;
class PriceTotaller
int countBooks = 0;
countBooks += 1;
priceBooks += book.Price;
class Test
Console.WriteLine($" {b.Title}");
AddBooks(bookDB);
// method Test.PrintTitle:
bookDB.ProcessPaperbackBooks(PrintTitle);
// a PriceTotaller object:
bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);
totaller.AveragePrice());
/* Output:
*/
Programación sólida
Declaración de un delegado.
C#
Cada tipo de delegado describe el número y los tipos de argumentos, y el tipo del
valor devuelto de los métodos que puede encapsular. Siempre que se necesite un
nuevo conjunto de tipos de argumentos o de tipos de valores devueltos, se debe
declarar un nuevo tipo de delegado.
C#
bookDB.ProcessPaperbackBooks(PrintTitle);
C#
bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);
Llamada a un delegado.
Después de haber creado un objeto delegado, este suele pasarse a otro código
que llamará al delegado. La llamada a un objeto delegado se realiza mediante la
utilización del nombre de dicho objeto, seguido de los argumentos entre
paréntesis que se deben pasar al delegado. A continuación se expone un ejemplo
de llamada a un delegado:
C#
processBook(b);
Consulte también
Guía de programación de C#
Eventos
Delegados
Matrices (Guía de programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
Puede almacenar varias variables del mismo tipo en una estructura de datos de matriz.
Puede declarar una matriz mediante la especificación del tipo de sus elementos. Si
quiere que la matriz almacene elementos de cualquier tipo, puede especificar object
como su tipo. En el sistema de tipos unificado de C#, todos los tipos, los predefinidos y
los definidos por el usuario, los tipos de referencia y los tipos de valores, heredan
directa o indirectamente de Object.
C#
type[] arrayName;
Ejemplo
Los ejemplos siguientes crean matrices unidimensionales, multidimensionales y
escalonadas:
C#
class TestArraysClass
// Alternative syntax.
int[] array3 = { 1, 2, 3, 4, 5, 6 };
int[,] multiDimensionalArray2 = { { 1, 2, 3 }, { 4, 5, 6 } };
// Set the values of the first array in the jagged array structure.
Los elementos de una matriz puede ser cualquier tipo, incluido un tipo de matriz.
Los tipos de matriz son tipos de referencia que proceden del tipo base abstracto
Array. Todas las matrices implementan IList y IEnumerable. Puede usar la
instrucción foreach para recorrer en iteración una matriz. Las matrices de
dimensión única también implementan IList<T> y IEnumerable<T>.
C#
int[] numbers = { 1, 2, 3, 4, 5 };
La clase Array proporciona muchos otros métodos útiles y propiedades para ordenar,
buscar y copiar matrices. En los ejemplos siguientes se usa la propiedad Rank para
mostrar el número de dimensiones de una matriz.
C#
class TestArraysClass
Vea también
Uso de matrices unidimensionales
Uso de matrices multidimensionales
Uso de matrices escalonadas
Utilizar foreach con matrices
Pasar matrices como argumentos
Matrices con tipo implícito
Guía de programación de C#
Colecciones
Cree una matriz unidimensional mediante el operador new; para ello, especifique el tipo
de elemento de matriz y el número de elementos. En el ejemplo siguiente se declara
una matriz de cinco enteros:
C#
Esta matriz contiene los elementos de array[0] a array[4] . Los elementos de la matriz
se inicializan en el valor predeterminado del tipo de elemento, 0 para los enteros.
Las matrices pueden almacenar cualquier tipo de elemento que se especifique, como en
el ejemplo siguiente, en el que se declara una matriz de cadenas:
C#
Inicialización de matriz
Puede inicializar los elementos de una matriz al declararla. El especificador de longitud
no es necesario porque la medida se infiere a partir del número de elementos de la lista
de inicialización. Por ejemplo:
C#
C#
string[] weekDays = new string[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri",
"Sat" };
C#
int[] array2 = { 1, 3, 5, 7, 9 };
Puede declarar una variable de matriz sin crearla, pero debe usar el operador new
cuando asigne una nueva matriz a esta variable. Por ejemplo:
C#
int[] array3;
C#
C#
Console.WriteLine(weekDays2[0]);
Console.WriteLine(weekDays2[1]);
Console.WriteLine(weekDays2[2]);
Console.WriteLine(weekDays2[3]);
Console.WriteLine(weekDays2[4]);
Console.WriteLine(weekDays2[5]);
Console.WriteLine(weekDays2[6]);
/*Output:
Sun
Mon
Tue
Wed
Thu
Fri
Sat
*/
Vea también
Array
Matrices
Matrices multidimensionales
Matrices escalonadas
Matrices multidimensionales (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Las matrices pueden tener varias dimensiones. Por ejemplo, la siguiente declaración crea
una matriz bidimensional de cuatro filas y dos columnas.
C#
C#
Inicialización de matriz
La matriz se puede inicializar en la declaración como se muestra en el ejemplo siguiente.
C#
// Two-dimensional array.
{ "five", "six" } };
// Three-dimensional array.
{ { 7, 8, 9 }, { 10, 11, 12 } } };
{ { 7, 8, 9 }, { 10, 11, 12 } } };
System.Console.WriteLine(array2D[0, 0]);
System.Console.WriteLine(array2D[0, 1]);
System.Console.WriteLine(array2D[1, 0]);
System.Console.WriteLine(array2D[1, 1]);
System.Console.WriteLine(array2D[3, 0]);
System.Console.WriteLine(array2Db[1, 0]);
System.Console.WriteLine(array3Da[1, 0, 1]);
System.Console.WriteLine(array3D[1, 1, 2]);
var total = 1;
total *= array3D.GetLength(i);
// Output:
// 1
// 2
// 3
// 4
// 7
// three
// 8
// 12
// 12 equals 12
C#
int[,] array4 = { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
Si opta por declarar una variable de matriz sin inicializarla, deberá usar el operador new
para asignar una matriz a la variable. El uso de new se muestra en el ejemplo siguiente.
C#
int[,] array5;
C#
array5[2, 1] = 25;
C#
Vea también
Guía de programación de C#
Matrices
Matrices unidimensionales
Matrices escalonadas
Matrices escalonadas (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
Una matriz escalonada es una matriz cuyos elementos son matrices, posiblemente de
diferentes tamaños. A veces, una matriz escalonada se denomina "matriz de matrices".
En los ejemplos siguientes se muestra cómo declarar, inicializar y acceder a matrices
escalonadas.
La siguiente es una declaración de una matriz unidimensional que tiene tres elementos,
cada uno de los cuales es una matriz unidimensional de enteros:
C#
Para poder usar jaggedArray , se deben inicializar sus elementos. Puede inicializar los
elementos de esta forma:
C#
Cada uno de los elementos es una matriz unidimensional de enteros. El primer elemento
es una matriz de 5 enteros, el segundo es una matriz de 4 enteros y el tercero es una
matriz de 2 enteros.
También es posible usar inicializadores para rellenar los elementos de matriz con
valores, en cuyo caso no es necesario el tamaño de la matriz. Por ejemplo:
C#
C#
int[][] jaggedArray2 = new int[][]
new int[] { 1, 3, 5, 7, 9 },
new int[] { 0, 2, 4, 6 },
};
Puede usar la siguiente forma abreviada. Tenga en cuenta que no puede omitir el
operador new de la inicialización de elementos porque no hay ninguna inicialización
predeterminada para los elementos:
C#
int[][] jaggedArray3 =
new int[] { 1, 3, 5, 7, 9 },
new int[] { 0, 2, 4, 6 },
};
Una matriz escalonada es una matriz de matrices y, por consiguiente, sus elementos son
tipos de referencia y se inicializan en null .
C#
jaggedArray3[0][1] = 77;
jaggedArray3[2][1] = 88;
C#
};
Puede tener acceso a los elementos individuales como se muestra en este ejemplo, que
muestra el valor del elemento [1,0] de la primera matriz (valor 5 ):
C#
C#
System.Console.WriteLine(jaggedArray4.Length);
devuelve un valor de 3.
Ejemplo
En este ejemplo, se crea una matriz cuyos elementos son matrices. Cada uno de los
elementos de matriz tiene un tamaño diferente.
C#
class ArrayTest
System.Console.Write("{0}{1}", arr[i][j], j ==
(arr[i].Length - 1) ? "" : " ");
System.Console.WriteLine();
System.Console.ReadKey();
}
/* Output:
Element(0): 1 3 5 7 9
Element(1): 2 4 6 8
*/
Vea también
Array
Guía de programación de C#
Matrices
Matrices unidimensionales
Matrices multidimensionales
Uso de foreach con matrices (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
La instrucción foreach ofrece una manera sencilla y limpia de iterar los elementos de
una matriz.
C#
// Output: 4 5 6 1 2 3 -2 -1 0
C#
// int[,] numbers2D = { { 9, 99 }, { 3, 33 }, { 5, 55 } };
// Output: 9 99 3 33 5 55
En cambio, con las matrices multidimensionales, usar un bucle for anidado ofrece un
mayor control sobre el orden en el que se procesan los elementos de la matriz.
Vea también
Array
Guía de programación de C#
Matrices
Matrices unidimensionales
Matrices multidimensionales
Matrices escalonadas
Pasar matrices como argumentos (Guía
de programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
Las matrices se pueden pasar como argumentos a parámetros de método. Dado que las
matrices son tipos de referencia, el método puede cambiar el valor de los elementos.
C#
int[] theArray = { 1, 3, 5, 7, 9 };
PrintArray(theArray);
C#
// Method code.
Puede inicializar y pasar una nueva matriz en un solo paso, como se muestra en el
ejemplo siguiente.
C#
Ejemplo
En el ejemplo siguiente, una matriz de cadenas se inicializa y pasa como un argumento
a un método DisplayArray para cadenas. El método muestra los elementos de la matriz.
A continuación, el método ChangeArray invierte los elementos de la matriz y, después, el
método ChangeArrayElements modifica los tres primeros elementos de la matriz.
Después de cada devolución de método, el método DisplayArray muestra que pasar
una matriz por valor no impide que se cambien los elementos de la matriz.
C#
using System;
class ArrayExample
arr[0] = "Mon";
arr[1] = "Wed";
arr[2] = "Fri";
}
DisplayArray(weekDays);
Console.WriteLine();
ChangeArray(weekDays);
DisplayArray(weekDays);
Console.WriteLine();
ChangeArrayElements(weekDays);
DisplayArray(weekDays);
//
//
C#
int[,] theArray = { { 1, 2 }, { 2, 3 }, { 3, 4 } };
Print2DArray(theArray);
C#
// Method code.
Puede inicializar y pasar una matriz nueva en un solo paso, como se muestra en el
ejemplo siguiente:
C#
Ejemplo
En el ejemplo siguiente, una matriz bidimensional de enteros se inicializa y pasa al
método Print2DArray . El método muestra los elementos de la matriz.
C#
class ArrayClass2D
System.Console.WriteLine("Element({0},{1})={2}", i, j,
arr[i, j]);
System.Console.ReadKey();
}
/* Output:
Element(0,0)=1
Element(0,1)=2
Element(1,0)=3
Element(1,1)=4
Element(2,0)=5
Element(2,1)=6
Element(3,0)=7
Element(3,1)=8
*/
Vea también
Guía de programación de C#
Matrices
Matrices unidimensionales
Matrices multidimensionales
Matrices escalonadas
Matrices con asignación implícita de
tipos (Guía de programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Puede crear una matriz con tipo implícito en la que se deduce el tipo de la instancia de
matriz de los elementos especificados en el inicializador de matriz. Las reglas de
cualquier variable con tipo implícito también se aplican a las matrices con tipo implícito.
Para más información, vea Variables locales con asignación implícita de tipos.
Normalmente, se usan matrices con tipo implícito en expresiones de consulta junto con
tipos anónimos e inicializadores de objeto y colección.
En los ejemplos siguientes, se muestra cómo crear una matriz con tipo implícito:
C#
class ImplicitlyTypedArraySample
var c = new[]
new[]{1,2,3,4},
new[]{5,6,7,8}
};
var d = new[]
};
En el ejemplo anterior observe que, con las matrices con tipo implícito, no se usan
corchetes en el lado izquierdo de la instrucción de inicialización. Tenga en cuenta
también que las matrices escalonadas se inicializan mediante new [] al igual que las
matrices unidimensionales.
Matrices con tipo implícito en inicializadores de
objeto
Al crear un tipo anónimo que contiene una matriz, esta debe tener tipo implícito en el
inicializador de objeto del tipo. En el ejemplo siguiente, contacts es una matriz con
tipos implícitos anónimos, cada uno de los cuales contiene una matriz denominada
PhoneNumbers . Tenga en cuenta que la palabra clave var no se usa dentro de los
inicializadores de objeto.
C#
new {
},
new {
};
Vea también
Guía de programación de C#
Variables locales con asignación implícita de tipos
Matrices
Tipos anónimos
Inicializadores de objeto y colección
var
LINQ en C#
Cadenas y literales de cadena
Artículo • 10/02/2023 • Tiempo de lectura: 17 minutos
Una cadena es un objeto de tipo String cuyo valor es texto. Internamente, el texto se
almacena como una colección secuencial de solo lectura de objetos Char. No hay
ningún carácter que finalice en NULL al final de una cadena de C#; por lo tanto, la
cadena de C# puede contener cualquier número de caracteres nulos insertados ("\0"). La
propiedad Length de una cadena representa el número de objetos Char que contiene,
no el número de caracteres Unicode. Para obtener acceso a los puntos de código
Unicode individuales de una cadena, use el objeto StringInfo.
C#
string message1;
// Initialize to null.
El operador new no se usa para crear un objeto de cadena, salvo cuando se inicialice la
cadena con una matriz de caracteres.
Inicialice una cadena con el valor constante Empty para crear un objeto String cuya
cadena tenga longitud cero. La representación literal de la cadena de una cadena de
longitud cero es "". Mediante la inicialización de las cadenas con el valor Empty en lugar
de null, puede reducir las posibilidades de que se produzca una excepción
NullReferenceException. Use el método estático IsNullOrEmpty(String) para comprobar
el valor de una cadena antes de intentar obtener acceso a ella.
C#
s1 += s2;
System.Console.WriteLine(s1);
Dado que una "modificación" de cadena es en realidad una creación de cadena, debe
tener cuidado al crear referencias a las cadenas. Si crea una referencia a una cadena y
después "modifica" la cadena original, la referencia seguirá apuntando al objeto original
en lugar de al objeto nuevo creado al modificarse la cadena. El código siguiente muestra
este comportamiento:
C#
str1 += "World";
System.Console.WriteLine(str2);
//Output: Hello
Para más información acerca de cómo crear cadenas nuevas basadas en modificaciones
como las operaciones de buscar y reemplazar en la cadena original, consulte
Modificación del contenido de cadenas.
C#
/* Output:
Row 1
Row 2
Row 3
*/
C#
//Output: C:\Users\scoleridge\Documents\
/* Output:
*/
C#
""";
</body>
<footer>
</footer>
</element >
""";
"""";
C#
// CS8999: Line does not start with the same whitespace as the closing line
A line of text.
""";
Los dos primeros ejemplos no son válidos porque los literales de cadena sin formato de
varias líneas requieren la secuencia de comillas de apertura y cierre en su propia línea. El
tercer ejemplo no es válido porque se anula la sangría del texto de la secuencia de
comillas de cierre.
C#
"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"
""";
Compare ese texto con el texto equivalente de nuestro ejemplo de serialización JSON,
que no usa esta nueva característica.
\0 Null 0x0000
\a Alerta 0x0007
\b Retroceso 0x0008
2 Advertencia
7 Nota
En tiempo de compilación, las cadenas textuales se convierten en cadenas
normales con las mismas secuencias de escape. Por lo tanto, si se muestra una
cadena textual en la ventana Inspección del depurador, verá los caracteres de
escape agregados por el compilador, no la versión textual del código fuente. Por
ejemplo, la cadena textual @"C:\files.txt" aparecerá en la ventana de inspección
como "C:\files.txt".
Cadenas de formato
Una cadena de formato es una cadena cuyo contenido se determina de manera
dinámica en tiempo de ejecución. Las cadenas de formato se crean mediante la
inserción de expresiones interpoladas o marcadores de posición entre llaves dentro de
una cadena. Todo lo incluido entre llaves ( {...} ) se resolverá en un valor y se generará
como una cadena con formato en tiempo de ejecución. Existen dos métodos para crear
cadenas de formato: interpolación de cadenas y formato compuesto.
Interpolación de cadenas
Disponible en C# 6.0 y versiones posteriores, las cadenas interpoladas se identifican por
el carácter especial $ e incluyen expresiones interpoladas entre llaves. Si no está
familiarizado con la interpolación de cadenas, consulte el tutorial interactivo
Interpolación de cadenas en C# para obtener información general rápidamente.
C#
// Output:
A partir de C# 11, puede combinar literales de cadena sin formato con interpolaciones
de cadenas. La cadena de formato se inicia y termina con tres o más comillas dobles
sucesivas. Si la cadena de salida debe contener el carácter { o } , puede usar caracteres
$ adicionales para especificar cuántos caracteres { y } comienzan y terminan una
interpolación. Todas las secuencias de menos caracteres { o } se incluye en la salida. En
el ejemplo siguiente se muestra cómo puede usar esa característica para mostrar la
distancia de un punto desde el origen y colocar el punto entre llaves:
C#
int X = 2;
int Y = 3;
Console.WriteLine(pointMessage);
// Output:
Formatos compuestos
String.Format emplea marcadores de posición entre llaves para crear una cadena de
formato. Los resultados de este ejemplo son similares a la salida del método de
interpolación de cadenas usado anteriormente.
C#
// Output:
Para más información sobre cómo dar formato a los tipos .NET, consulte Aplicación de
formato a tipos en .NET.
Subcadenas
Una subcadena es cualquier secuencia de caracteres que se encuentra en una cadena.
Use el método Substring para crear una nueva cadena de una parte de la cadena
original. Puede buscar una o más apariciones de una subcadena con el método IndexOf.
Use el método Replace para reemplazar todas las apariciones de una subcadena
especificada por una nueva cadena. Al igual que el método Substring, Replace devuelve
en realidad una cadena nueva y no modifica la cadena original. Para más información,
consulte Cómo: Buscar cadenas y Procedimiento para modificar el contenido de
cadenas.
C#
System.Console.WriteLine(s3.Substring(7, 2));
// Output: "C#"
System.Console.WriteLine(s3.Replace("C#", "Basic"));
// index = 7
C#
System.Console.Write(s5[s5.Length - i - 1]);
Si el método String no proporciona la funcionalidad que debe tener para modificar los
caracteres individuales de una cadena, puede usar un objeto StringBuilder para
modificar los caracteres individuales "en contexto" y, después, crear una cadena para
almacenar los resultados mediante el método StringBuilder. En el ejemplo siguiente, se
supone que debe modificar la cadena original de una manera determinada y, después,
almacenar los resultados para un uso futuro:
C#
string question = "hOW DOES mICROSOFT wORD DEAL WITH THE cAPS lOCK KEY?";
if (System.Char.IsLower(sb[j]) == true)
sb[j] = System.Char.ToUpper(sb[j]);
sb[j] = System.Char.ToLower(sb[j]);
System.Console.WriteLine(corrected);
// Output: How does Microsoft Word deal with the Caps Lock key?
C#
string s = String.Empty;
C#
Console.WriteLine(tempStr);
Console.WriteLine(b);
Console.WriteLine(emptyStr.Length);
Console.WriteLine(newStr.Length);
// The following line raises a NullReferenceException.
//Console.WriteLine(nullStr.Length);
// The null character can be displayed and counted, like other chars.
Console.WriteLine("*" + s1 + "*");
Console.WriteLine("*" + s2 + "*");
Console.WriteLine(s2.Length);
C#
sb[0] = 'C';
System.Console.WriteLine(sb.ToString());
En este ejemplo, se usa un objeto StringBuilder para crear una cadena a partir de un
conjunto de tipos numéricos:
C#
sb.Append(i.ToString());
sb[0] = sb[9];
Artículos relacionados
En Modificación del contenido de las cadenas se muestran técnicas para
transformar las cadenas y modificar su contenido.
En Comparación de cadenas se muestra cómo realizar comparaciones ordinales y
culturales específicas de las cadenas.
En Concatenación de varias cadenasse muestran diversas formas de combinar
varias cadenas en una.
Análisis de cadenas mediante String.Split: contiene ejemplos de código que
muestran cómo usar el método String.Split para analizar cadenas.
En Búsqueda de cadenas se explica cómo usar la búsqueda con texto o patrones
específicos de las cadenas.
En Determinación de si una cadena representa un valor numérico se muestra cómo
analizar de forma segura una cadena para ver si tiene un valor numérico válido.
En Interpolación de cadenas se describe la característica de interpolación de
cadenas que proporciona una sintaxis adecuada para dar formato a las cadenas.
En Operaciones básicas de cadenas se proporcionan vínculos a artículos que usan
los métodos System.String y System.Text.StringBuilder para realizar operaciones
básicas de cadenas.
En Análisis de cadenas se describe cómo convertir representaciones de cadenas de
tipos base de .NET en instancias de los tipos correspondientes.
En Análisis de cadenas de fecha y hora en .NET se muestra cómo convertir una
cadena, como "01/24/2008", en un objeto System.DateTime.
En Comparación de cadenas se incluye información sobre cómo comparar cadenas
y se proporcionan ejemplos de C# y Visual Basic.
En Uso de la clase StringBuilder se describe cómo crear y modificar objetos de
cadena dinámicos con la clase StringBuilder.
En LINQ y cadenas se proporciona información sobre cómo realizar varias
operaciones de cadena utilizando consultas LINQ.
Procedimiento Determinar si una
cadena representa un valor numérico
(Guía de programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
C#
int i = 0;
string s = "108";
7 Nota
Una cadena puede contener solamente caracteres numéricos pero no ser válida
para el tipo cuyo método TryParse se está usando. Por ejemplo, "256" no es un
valor válido para byte pero sí para int . "98,6" no es un valor válido para int pero
sí para decimal .
Ejemplo
En los ejemplos siguientes se muestra cómo usar TryParse con representaciones de
cadena de los valores long , byte y decimal .
C#
long number1 = 0;
else
byte number2 = 0;
if (canConvert == true)
else
decimal number3 = 0;
if (canConvert == true)
else
Programación sólida
Los tipos numéricos primitivos también implementan el método estático Parse , que
produce una excepción si la cadena no es un número válido. TryParse es, en general,
más eficaz porque simplemente devuelve false si el número no es válido.
Seguridad de .NET
Use siempre los métodos TryParse o Parse para validar los datos proporcionados por
el usuario en controles como cuadros de texto y cuadros combinados.
Vea también
Procedimiento Convertir una matriz de bytes en un valor int
Procedimiento Convertir una cadena en un número
Procedimiento Convertir cadenas hexadecimales en tipos numéricos
Análisis de cadenas numéricas
Aplicación de formato a tipos
Indizadores (Guía de programación de
C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
Los indizadores permiten indizar las instancias de una clase o struct como matrices. El
valor indizado se puede establecer o recuperar sin especificar explícitamente un
miembro de tipo o de instancia. Son similares a propiedades, excepto en que sus
descriptores de acceso usan parámetros.
C#
using System;
class SampleCollection<T>
public T this[int i]
class Program
Console.WriteLine(stringCollection[0]);
// Hello, World.
7 Nota
C#
using System;
class SampleCollection<T>
int nextIndex = 0;
arr[nextIndex++] = value;
class Program
stringCollection.Add("Hello, World");
System.Console.WriteLine(stringCollection[0]);
// Hello, World.
Tenga en cuenta que => presenta el cuerpo de la expresión y que la palabra clave get
no se utiliza.
A partir de C# 7.0, los descriptores de acceso get y set se pueden implementar como
miembros con forma de expresión. En este caso, sí deben utilizarse las palabras clave
get y set . Por ejemplo:
C#
using System;
class SampleCollection<T>
public T this[int i]
class Program
Console.WriteLine(stringCollection[0]);
// Hello, World.
La palabra clave value se usa para definir el valor que va a asignar el descriptor de
acceso set .
Los indizadores no tienen que ser indizados por un valor entero; depende de usted
cómo definir el mecanismo de búsqueda concreto.
Secciones relacionadas
Utilizar indizadores
Indizadores en Interfaces
Vea también
Guía de programación de C#
Propiedades
Uso de indizadores (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 6 minutos
Los indizadores son una comodidad sintáctica que le permiten crear una clase,
estructura o interfaz a la que las aplicaciones cliente pueden acceder como una matriz.
El compilador generará una propiedad Item (o una propiedad con otro nombre si está
presente IndexerNameAttribute) y los métodos de descriptor de acceso adecuados. Los
indexadores se implementan con más frecuencia en tipos cuyo propósito principal
consiste en encapsular una matriz o colección interna. Por ejemplo, imagine que tiene
una clase TempRecord que representa la temperatura en grados Fahrenheit que se
registra en 10 momentos diferentes durante un período de 24 horas. La clase contiene
una matriz temps de tipo float[] para almacenar los valores de temperatura. Si
implementa un indizador en esta clase, los clientes pueden tener acceso a las
temperaturas en una instancia de TempRecord como float temp = tempRecord[4] en
lugar de como float temp = tempRecord.temps[4] . La notación del indizador no solo
simplifica la sintaxis para las aplicaciones cliente; también hace que la clase y su
finalidad sean más intuitivas para que otros desarrolladores las entiendan.
Para declarar un indizador en una clase o un struct, use la palabra clave this, como en
este ejemplo:
C#
// Indexer declaration
) Importante
Para obtener más información sobre cómo usar los indexadores con una interfaz, vea
Indizadores en interfaces.
La firma de un indexador consta del número y los tipos de sus parámetros formales. No
incluye el tipo de indizador ni los nombres de los parámetros formales. Si declara más
de un indexador en la misma clase, deben tener firmas diferentes.
Para proporcionar el indizador con un nombre que puedan usar otros lenguajes, use
System.Runtime.CompilerServices.IndexerNameAttribute, como se muestra en el
ejemplo siguiente:
C#
// Indexer declaration
[System.Runtime.CompilerServices.IndexerName("TheItem")]
public int this[int index]
Este indizador tendrá el nombre TheItem , ya que lo reemplaza el atributo de nombre del
indizador. De forma predeterminada, el nombre del indizador es Item .
Ejemplo 1
En el ejemplo siguiente, se muestra cómo declarar un campo de matriz privada, temps ,
como un indexador. El indexador permite el acceso directo a la instancia tempRecord[i] .
La alternativa a usar el indexador es declarar la matriz como un miembro public y tener
acceso directamente a sus miembros tempRecord.temps[i] .
C#
};
// Indexer declaration.
// If index is out of range, the temps array will throw the exception.
Tenga en cuenta que, cuando se evalúa el acceso de un indexador (por ejemplo, en una
instrucción Console.Write ), se invoca al descriptor de acceso get. Por tanto, si no hay
ningún descriptor de acceso get , se produce un error en tiempo de compilación.
C#
class Program
tempRecord[3] = 58.3F;
tempRecord[5] = 60.1F;
Console.ReadKey();
/* Output:
Element #0 = 56.2
Element #1 = 56.7
Element #2 = 56.5
Element #3 = 58.3
Element #4 = 58.8
Element #5 = 60.1
Element #6 = 65.9
Element #7 = 62.1
Element #8 = 59.2
Element #9 = 57.5
*/
Ejemplo 2
En el ejemplo siguiente se declara una clase que almacena los días de la semana. Un
descriptor de acceso get toma una cadena, el nombre de un día, y devuelve el entero
correspondiente. Por ejemplo, "Sunday" devuelve 0, "Monday" devuelve 1 y así
sucesivamente.
C#
class DayCollection
if (days[j] == day)
return j;
nameof(day),
Ejemplo de consumo 2
C#
class Program
Console.WriteLine(week["Fri"]);
try
Console.WriteLine(week["Made-up day"]);
catch (ArgumentOutOfRangeException e)
// Output:
// 5
// Day input must be in the form "Sun", "Mon", etc (Parameter 'day')
Ejemplo 3
En el ejemplo siguiente se declara una clase que almacena los días de la semana
mediante la enumeración System.DayOfWeek. Un descriptor de acceso get toma un
valor DayOfWeek , el nombre de un día, y devuelve el entero correspondiente. Por
ejemplo, DayOfWeek.Sunday devuelve 0, DayOfWeek.Monday devuelve 1, y así
sucesivamente.
C#
class DayOfWeekCollection
Day[] days =
};
if (days[j] == day)
return j;
nameof(day),
Ejemplo de consumo 3
C#
class Program
Console.WriteLine(week[DayOfWeek.Friday]);
try
Console.WriteLine(week[(DayOfWeek)43]);
catch (ArgumentOutOfRangeException e)
// Output:
// 5
Programación sólida
Hay dos formas principales en que se pueden mejorar la seguridad y confiabilidad de
los indexadores:
Establezca la accesibilidad de los descriptores de acceso get and set para que sea
tan restrictiva como razonable. Esto es importante para el descriptor de acceso
set en particular. Para más información, vea Restringir la accesibilidad del
descriptor de acceso.
Vea también
Guía de programación de C#
Indizadores
Propiedades
Indizadores en interfaces (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Los indexadores se pueden declarar en una interfaz. Los descriptores de acceso de los
indexadores de interfaz se diferencian de los descriptores de acceso de los indexadores
de clase de las maneras siguientes:
C#
//...
// Indexer declaration:
get;
set;
La firma de un indexador debe ser diferente de las firmas de los demás indexadores
declarados en la misma interfaz.
Ejemplo
En el siguiente ejemplo, se muestra cómo implementar indexadores de interfaz.
C#
// Indexer on an interface:
// Indexer declaration:
get;
set;
C#
test[i] = rand.Next();
/* Sample output:
Element #0 = 360877544
Element #1 = 327058047
Element #2 = 1913480832
Element #3 = 1519039937
Element #4 = 601472233
Element #5 = 323352310
Element #6 = 1422639981
Element #7 = 1797892494
Element #8 = 875761049
Element #9 = 393083859
*/
C#
string IIndexInterface.this[int index]
C#
C#
Vea también
Guía de programación de C#
Indizadores
Propiedades
Interfaces
Comparación entre propiedades e
indizadores (Guía de programación de
C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Los indexadores son como propiedades. Excepto por las diferencias que se muestran en
la tabla siguiente, todas las reglas que se definen para los descriptores de acceso de
propiedad se aplican también a los descriptores de acceso de indexador.
Propiedad. Indexador
Permite que los métodos se llamen Permite que se pueda tener acceso a los elementos de una
como si fueran miembros de datos colección interna de un objeto mediante la notación de
públicos. matriz en el propio objeto.
Admite la sintaxis abreviada con Admite miembros de cuerpo de expresión para obtener solo
Propiedades autoimplementadas. indexadores.
Consulte también
Guía de programación de C#
Indizadores
Propiedades
Eventos (Guía de programación de C#)
Artículo • 10/02/2023 • Tiempo de lectura: 2 minutos
Cuando ocurre algo interesante, los eventos habilitan una clase u objeto para notificarlo
a otras clases u objetos. La clase que envía (o genera) el evento recibe el nombre de
publicador y las clases que reciben (o controlan) el evento se denominan suscriptores.
Los eventos se suelen usar para indicar acciones del usuario, como los clics de los
botones o las selecciones de menú en las interfaces gráficas de usuario.
Secciones relacionadas
Para obtener más información, consulte:
Consulte también
EventHandler
Guía de programación de C#
Delegados
Crear controladores de eventos en Windows Forms
Procedimiento Suscribir y cancelar la
suscripción a eventos (Guía de
programación de C#)
Artículo • 10/03/2023 • Tiempo de lectura: 3 minutos
La suscripción a un evento publicado por otra clase se realiza cuando quiere escribir
código personalizado al que se llama cuando se produce ese evento. Por ejemplo,
puede suscribirse al evento click de un botón para que la aplicación realice alguna
operación cuando el usuario haga clic en el botón.
3. Haga doble clic en el evento que quiera crear, por ejemplo, el evento Load .
C#
C#
C#
C#
publisher.RaiseCustomEvent += HandleCustomEvent;
C#
public Form1()
InitializeComponent();
MessageBox.Show(((MouseEventArgs)e).Location.ToString());
};
C#
Console.WriteLine(s);
};
C#
publisher.RaiseCustomEvent -= HandleCustomEvent;
C#
7 Nota
Aunque los eventos de las clases que defina se pueden basar en cualquier tipo de
delegado válido, incluidos los delegados que devuelven un valor, por lo general se
recomienda que base los eventos en el patrón de .NET mediante EventHandler, como se
muestra en el ejemplo siguiente.
Message = message;
C#
C#
C#
C#
Ejemplo
En el siguiente ejemplo se muestran los pasos anteriores mediante el uso de una clase
EventArgs personalizada y EventHandler<TEventArgs> como tipo de evento.
C#
using System;
namespace DotNetEvents
Message = message;
class Publisher
if (raiseEvent != null)
raiseEvent(this, e);
class Subscriber
_id = id;
pub.RaiseCustomEvent += HandleCustomEvent;
class Program
pub.DoSomething();
Console.ReadLine();
Vea también
Delegate
Guía de programación de C#
Eventos
Delegados
Procedimiento Producir eventos de una
clase base en clases derivadas (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
Al crear una clase que se pueda usar como clase base para otras clases, debe considerar
el hecho de que los eventos son un tipo especial de delegado que solo se pueden
invocar desde la clase que los haya declarado. Las clases derivadas no pueden invocar
directamente a eventos declarados en la clase base. Aunque a veces pueda querer un
evento que solo la clase base pueda generar, en la mayoría de los casos debería
habilitar la clase derivada para invocar a eventos de clase base. Para ello, puede crear un
método de invocación protegido en la clase base que encapsula el evento. Al llamar o
invalidar a este método de invocación, las clases derivadas pueden invocar directamente
al evento.
7 Nota
No declare eventos virtuales en una clase base y los invalide en una clase derivada.
El compilador de C# no los controla correctamente y no es posible decir si un
suscriptor del evento derivado se está suscribiendo realmente al evento de clase
base.
Ejemplo
C#
namespace BaseClassEvents
NewArea = area;
ShapeChanged?.Invoke(this, e);
_radius = radius;
_radius = d;
OnShapeChanged(new ShapeEventArgs(_area));
base.OnShapeChanged(e);
Console.WriteLine("Drawing a circle");
_length = length;
_width = width;
_length = length;
_width = width;
OnShapeChanged(new ShapeEventArgs(_area));
base.OnShapeChanged(e);
Console.WriteLine("Drawing a rectangle");
public ShapeContainer()
_list.Add(shape);
shape.ShapeChanged += HandleShapeChanged;
shape.Draw();
class Test
container.AddShape(circle);
container.AddShape(rectangle);
circle.Update(57);
rectangle.Update(7, 7);
Console.ReadKey();
/* Output:
Drawing a circle
Drawing a rectangle
*/
Consulte también
Guía de programación de C#
Eventos
Delegados
Modificadores de acceso
Crear controladores de eventos en Windows Forms
Procedimiento Implementar eventos de
interfaz (Guía de programación de C#)
Artículo • 10/03/2023 • Tiempo de lectura: 3 minutos
C#
namespace ImplementInterfaceEvents
// class members
void ChangeShape()
OnShapeChanged(new MyEventArgs(/*arguments*/));
ShapeChanged?.Invoke(this, e);
Ejemplo
En el ejemplo siguiente se muestra cómo controlar la situación menos común en que la
clase hereda de dos o más interfaces y cada interfaz tiene un evento con el mismo
nombre. En esta situación, debe proporcionar una implementación de interfaz explícita
para uno de los eventos como mínimo. Al escribir una implementación de interfaz
explícita para un evento, también debe escribir los descriptores de acceso del evento
add y remove . Normalmente, los proporciona el compilador, pero en este caso no es
posible.
Al proporcionar sus propios descriptores de acceso, puede especificar si los dos eventos
se representan mediante el mismo evento en la clase o mediante eventos diferentes. Por
ejemplo, si los eventos deben provocarse en momentos diferentes según las
especificaciones de la interfaz, puede asociar cada evento a una implementación distinta
en su clase. En el ejemplo siguiente, los suscriptores determinan qué evento OnDraw
recibirán al convertir la referencia de forma en IShape o en IDrawingObject .
C#
namespace WrapTwoInterfaceEvents
using System;
// the object.
// the shape.
// PreDrawEvent
#region IDrawingObjectOnDraw
add
lock (objectLock)
{
PreDrawEvent += value;
remove
lock (objectLock)
{
PreDrawEvent -= value;
#endregion
// PostDrawEvent
add
lock (objectLock)
{
PostDrawEvent += value;
remove
lock (objectLock)
{
PostDrawEvent -= value;
PreDrawEvent?.Invoke(this, EventArgs.Empty);
Console.WriteLine("Drawing a shape.");
PostDrawEvent?.Invoke(this, EventArgs.Empty);
IDrawingObject d = (IDrawingObject)shape;
d.OnDraw += d_OnDraw;
}
IShape d = (IShape)shape;
d.OnDraw += d_OnDraw;
}
shape.Draw();
System.Console.ReadKey();
/* Output:
Drawing a shape.
*/
Consulte también
Guía de programación de C#
Eventos
Delegados
Implementación de interfaz explícita
Procedimiento para generar eventos de una clase base en clases derivadas
Procedimiento Implementar
descriptores de acceso de eventos
personalizados (Guía de programación
de C#)
Artículo • 10/03/2023 • Tiempo de lectura: 2 minutos
Ejemplo
En el ejemplo siguiente se muestra cómo implementar descriptores de acceso de
eventos add y remove personalizados. Aunque puede sustituir cualquier código dentro
de los descriptores de acceso, recomendamos que bloquee el evento antes de agregar o
quitar un nuevo método de control de eventos.
C#
add
lock (objectLock)
PreDrawEvent += value;
remove
lock (objectLock)
PreDrawEvent -= value;
Vea también
Eventos
event
Parámetros de tipos genéricos (Guía de
programación de C#)
Artículo • 04/01/2023 • Tiempo de lectura: 2 minutos
C#
C#
Considere el uso de T como el nombre del parámetro de tipo para los tipos con un
parámetro de tipo de una sola letra.
C#
C#
La regla de análisis de código CA1715 puede usarse para asegurarse de que los
parámetros de tipo se denominan apropiadamente.
Consulte también
System.Collections.Generic
Guía de programación de C#
Genéricos
Diferencias entre plantillas de C++ y tipos genéricos de C#
Restricciones de tipos de parámetros
(Guía de programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 13 minutos
Las restricciones informan al compilador sobre las capacidades que debe tener un
argumento de tipo. Sin restricciones, el argumento de tipo puede ser cualquier tipo. El
compilador solo puede suponer los miembros de System.Object, que es la clase base
fundamental de los tipos .NET. Para más información, vea Por qué usar restricciones. Si
el código de cliente usa un tipo que no cumple una restricción, el compilador emite un
error. Las restricciones se especifican con la palabra clave contextual where . En la tabla
siguiente se muestran los distintos tipos de restricciones:
Restricción Descripción
where T : El argumento de tipo debe ser un tipo de valor que no acepta valores NULL. Para
struct más información sobre los tipos de valor que admiten un valor NULL, consulte
Tipos de valor que admiten un valor NULL. Todos los tipos de valor tienen un
constructor sin parámetros accesible, por lo que la restricción struct implica la
restricción new() y no se puede combinar con la restricción new() . No puede
combinar la restricción struct con la restricción unmanaged .
where T : El argumento de tipo debe ser un tipo de referencia. Esta restricción se aplica
class también a cualquier clase, interfaz, delegado o tipo de matriz. En un contexto que
acepta valores NULL, T debe ser un tipo de referencia que no acepta valores NULL.
where T : El argumento de tipo debe ser un tipo de referencia, que acepte o no valores NULL.
class? Esta restricción se aplica también a cualquier clase, interfaz, delegado o tipo de
matriz.
where T : El argumento de tipo debe ser un tipo que no acepta valores NULL. El argumento
notnull puede ser un tipo de referencia que no acepta valores NULL, o bien un tipo de
valor que no acepta valores NULL.
where T : El argumento de tipo debe ser un tipo no administrado que no acepta valores
unmanaged NULL. La restricción unmanaged implica la restricción struct y no se puede
combinar con las restricciones struct ni new() .
Restricción Descripción
where T : El argumento de tipo debe tener un constructor sin parámetros público. Cuando se
new() usa conjuntamente con otras restricciones, la restricción new() debe especificarse
en último lugar. La restricción new() no se puede combinar con las restricciones
struct ni unmanaged .
where T : El argumento de tipo proporcionado por T debe ser o se debe derivar del
U argumento proporcionado para U . En un contexto que admite un valor NULL, si U
puede ser un tipo de referencia que no acepta valores NULL, T debe ser un tipo de
referencia que no acepta valores NULL. Si U es un tipo de referencia que admite un
valor NULL, T puede aceptar valores NULL o no.
C#
public Employee(string name, int id) => (Name, ID) = (name, id);
head = n;
current = current.Next;
public T? FindFirstOccurrence(string s)
T? t = null;
if (current.Data.Name == s)
t = current.Data;
break;
else
current = current.Next;
return t;
C#
// ...
C#
System.Console.WriteLine(s == t);
string s1 = "target";
string s2 = sb.ToString();
OpEqualsTest<string>(s1, s2);
C#
class Base { }
where U : struct
{ }
C#
public class List<T>
C#
Restricción notnull
Puede usar la restricción notnull para especificar que el argumento de tipo debe ser un
tipo de valor que no acepta valores NULL o un tipo de referencia que no acepta valores
NULL. A diferencia de la mayoría de las demás restricciones, si un argumento de tipo
infringe la restricción notnull , el compilador genera una advertencia en lugar de un
error.
La restricción notnull tiene efecto solo cuando se usa en un contexto que admite un
valor NULL. Si agrega la restricción notnull en un contexto en el que se desconocen los
valores NULL, el compilador no genera advertencias ni errores para las infracciones de la
restricción.
Restricción class
La restricción class en un contexto que acepta valores NULL especifica que el
argumento de tipo debe ser un tipo de referencia que no acepta valores NULL. En un
contexto que admite un valor NULL, cuando un argumento de tipo es un tipo de
referencia que admite un valor NULL, el compilador genera una advertencia.
Restricción default
La incorporación de tipos de referencia que aceptan valores NULL complica el uso de
T? en un método o tipo genérico. T? se puede usar con la restricción struct o class ,
pero una de ellas debe estar presente. Cuando se ha usado la restricción class , T? se
refiere al tipo de referencia que acepta valores NULL para T . A partir de C# 9, T? se
puede usar cuando no se aplica ninguna restricción. En ese caso, T? se interpreta como
T? para tipos de valor y los tipos de referencia. Sin embargo, si T es una instancia de
Dado que T? ahora se puede usar sin las restricciones class o struct , pueden surgir
ambigüedades en invalidaciones o implementaciones de interfaz explícitas. En ambos
casos, la invalidación no incluye las restricciones, pero las hereda de la clase base.
Cuando la clase base no aplica las restricciones class o struct , las clases derivadas
deben especificar de algún modo que una invalidación se aplica al método base sin
ninguna restricción. Ahí es cuando el método derivado aplica la restricción default . La
restricción default no aclara ni la restricción class ni la struct .
Restricción no administrada
Puede usar la restricción unmanaged para especificar que el parámetro de tipo debe ser
un tipo no administrado que no acepta valores NULL. La restricción unmanaged permite
escribir rutinas reutilizables para trabajar con tipos que se pueden manipular como
bloques de memoria, como se muestra en el ejemplo siguiente:
C#
Byte* p = (byte*)&argument;
result[i] = *p++;
return result;
Restricciones de delegado
Puede usar System.Delegate o System.MulticastDelegate como una restricción de clase
base. CLR siempre permitía esta restricción, pero el lenguaje C# no la permitía. La
restricción System.Delegate permite escribir código que funciona con los delegados en
un modo con seguridad de tipos. En el código siguiente se define un método de
extensión que combina dos delegados siempre y cuando sean del mismo tipo:
C#
Puede usar el método anterior para combinar delegados que sean del mismo tipo:
C#
combined!();
Restricciones de enumeración
También puede especificar el tipo System.Enum como una restricción de clase base. CLR
siempre permitía esta restricción, pero el lenguaje C# no la permitía. Los genéricos que
usan System.Enum proporcionan programación con seguridad de tipos para almacenar
en caché los resultados de usar los métodos estáticos en System.Enum . En el ejemplo
siguiente se buscan todos los valores válidos para un tipo de enumeración y, después,
se compila un diccionario que asigna esos valores a su representación de cadena.
C#
return result;
Podría usarla como se muestra en el ejemplo siguiente para crear una enumeración y
compilar un diccionario con sus nombres y valores:
C#
enum Rainbow
Red,
Orange,
Yellow,
Green,
Blue,
Indigo,
Violet
C#
Console.WriteLine($"{pair.Key}:\t{pair.Value}");
C#
C#
IAdditionSubtraction<T> left,
IAdditionSubtraction<T> right);
IAdditionSubtraction<T> left,
IAdditionSubtraction<T> right);
Consulte también
System.Collections.Generic
Guía de programación de C#
Introducción a los genéricos
Clases genéricas
new (restricción)
Clases genéricas (Guía de programación
de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 4 minutos
Las clases genéricas encapsulan operaciones que no son específicas de un tipo de datos
determinado. El uso más común de las clases genéricas es con colecciones como listas
vinculadas, tablas hash, pilas, colas y árboles, entre otros. Las operaciones como la
adición y eliminación de elementos de la colección se realizan básicamente de la misma
manera independientemente del tipo de datos que se almacenan.
Normalmente, crea clases genéricas empezando con una clase concreta existente, y
cambiando tipos en parámetros de tipo de uno en uno hasta que alcanza el equilibrio
óptimo de generalización y facilidad de uso. Al crear sus propias clases genéricas, entre
las consideraciones importantes se incluyen las siguientes:
Como norma, cuantos más tipos pueda parametrizar, más flexible y reutilizable
será su código. En cambio, demasiada generalización puede crear código que sea
difícil de leer o entender para otros desarrolladores.
Las restricciones, si existen, que se van a aplicar a los parámetros de tipo (Vea
Restricciones de parámetros de tipo).
Como las clases genéricas pueden servir como clases base, las mismas
consideraciones de diseño se aplican aquí con clases no genéricas. Vea las reglas
sobre cómo heredar de clases base genéricas posteriormente en este tema.
Para obtener un ejemplo de una clase genérica simple, vea Introducción a los genéricos.
Las reglas para los parámetros de tipo y las restricciones tienen varias implicaciones
para el comportamiento de clase genérico, especialmente respecto a la herencia y a la
accesibilidad de miembros. Antes de continuar, debe entender algunos términos. En una
clase genérica Node<T>, , el código de cliente puede hacer referencia a la clase
especificando un argumento de tipo, para crear un tipo construido cerrado ( Node<int> );
o dejando sin especificar el parámetro de tipo, por ejemplo, cuando se especifica una
clase base genérica, para crear un tipo construido abierto ( Node<T> ). Las clases genéricas
pueden heredar de determinadas clases base construidas abiertas o construidas
cerradas:
C#
class BaseNode { }
class BaseNodeGeneric<T> { }
// concrete type
Las clases no genéricas, en otras palabras, concretas, pueden heredar de clases base
construidas cerradas, pero no desde clases construidas abiertas ni desde parámetros de
tipo porque no hay ninguna manera en tiempo de ejecución para que el código de
cliente proporcione el argumento de tipo necesario para crear instancias de la clase
base.
C#
//No error
//Generates an error
//Generates an error
//class Node3 : T {}
Las clases genéricas que heredan de tipos construidos abiertos deben proporcionar
argumentos de tipo para cualquier parámetro de tipo de clase base que no se comparta
mediante la clase heredada, como se demuestra en el código siguiente:
C#
//No error
//No error
//Generates an error
Las clases genéricas que heredan de tipos construidos abiertos deben especificar
restricciones que son un superconjunto de las restricciones del tipo base, o que las
implican:
C#
Los tipos genéricos pueden usar varios parámetros de tipo y restricciones, de la manera
siguiente:
C#
where U : System.IComparable<U>
where V : new()
{ }
C#
Si una clase genérica implementa una interfaz, todas las instancias de esa clase se
pueden convertir en esa interfaz.
Consulte también
System.Collections.Generic
Guía de programación de C#
Genéricos
Guardar el estado de los enumeradores
Un puzle de herencia, parte uno
Interfaces genéricas (Guía de
programación de C#)
Artículo • 10/02/2023 • Tiempo de lectura: 5 minutos
A menudo es útil definir interfaces para las clases de colección genéricas o para las
clases genéricas que representan elementos de la colección. Para evitar operaciones de
conversión boxing y unboxing en tipos de valor, es mejor usar interfaces genéricas,
como IComparable<T>, en clases genéricas. En la biblioteca de clases de .NET se
definen varias interfaces genéricas para usarlas con las clases de colección del espacio
de nombres System.Collections.Generic. Para más información sobre estas interfaces,
consulte Interfaces genéricas.
usar el método CompareTo genérico con los elementos de lista. En este ejemplo, los
elementos de lista son una clase simple, Person , que implementa IComparable<Person> .
C#
next = null;
data = t;
head = null;
n.Next = head;
head = n;
current = current.Next;
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator()
return GetEnumerator();
return;
bool swapped;
do
swapped = false;
if (current.Data.CompareTo(current.next.Data) > 0)
current.next = current.next.next;
tmp.next = current;
if (previous == null)
head = tmp;
else
previous.next = tmp;
previous = tmp;
swapped = true;
else
previous = current;
current = current.next;
} while (swapped);
string name;
int age;
name = s;
age = i;
"Franscoise",
"Bill",
"Li",
"Sandra",
"Gunnar",
"Alok",
"Hiroyuki",
"Maria",
"Alessandro",
"Raul"
};
int[] ages = new int[] { 45, 19, 28, 23, 18, 9, 108, 72, 30, 35 };
System.Console.WriteLine(p.ToString());
list.BubbleSort();
System.Console.WriteLine(p.ToString());
C#
C#
Las reglas de herencia que se aplican a las clases también se aplican a las interfaces:
C#
interface IMonth<T> { }
C#
interface IBaseInterface<T> { }
C#
interface IBaseInterface1<T> { }
Las reglas que controlan la sobrecarga de métodos son las mismas para los métodos
incluidos en las clases genéricas, los structs genéricos o las interfaces genéricas. Para
obtener más información, vea Métodos genéricos.
A partir de C# 11, las interfaces pueden declarar miembros static abstract o static
virtual . Las interfaces que declaran miembros static abstract o static virtual son
casi siempre interfaces genéricas. El compilador debe resolver las llamadas a los
métodos static virtual y static abstract en tiempo de compilación. Los métodos
static virtual y static abstract declarados en interfaces no tienen un mecanismo de
Consulte también
Guía de programación de C#
Introducción a los genéricos
interface
Genéricos
Métodos genéricos (Guía de
programación de C#)
Artículo • 04/01/2023 • Tiempo de lectura: 2 minutos
C#
T temp;
temp = lhs;
lhs = rhs;
rhs = temp;
En el siguiente ejemplo de código se muestra una manera de llamar al método con int
para el argumento de tipo:
C#
int a = 1;
int b = 2;
C#
Las mismas reglas para la inferencia de tipos se aplican a los métodos estáticos y a los
métodos de instancia. El compilador puede deducir los parámetros de tipo basados en
los argumentos de método que se pasan; no puede deducir los parámetros de tipo solo
desde un valor devuelto o una restricción. Por lo tanto, la inferencia de tipos no
funciona con métodos que no tienen parámetros. La inferencia de tipos se produce en
tiempo de compilación antes de que el compilador intente resolver las firmas de
método sobrecargadas. El compilador aplica la lógica de inferencia de tipos a todos los
métodos genéricos que comparten el mismo nombre. En el paso de resolución de
sobrecarga, el compilador incluye solo esos métodos genéricos en los que la inferencia
de tipos se ha realizado correctamente.
Dentro de una clase genérica, los métodos no genéricos pueden tener acceso a los
parámetros de tipo de nivel de clase, de la manera siguiente:
C#
class SampleClass<T>
Si define un método genérico que toma los mismos parámetros de tipo que la clase
contenedora, el compilador genera la advertencia CS0693 porque, dentro del ámbito
del método, el argumento que se ha proporcionado para el T interno oculta el
argumento que se ha proporcionado para el T externo. Si necesita la flexibilidad de
llamar a un método de la clase genérica con argumentos de tipo diferentes de los que
se han proporcionado cuando se ha creado una instancia de la clase, considere la
posibilidad de proporcionar otro identificador para el parámetro de tipo del método,
como se muestra en GenericList2<T> en el ejemplo siguiente.
C#
class GenericList<T>
// CS0693
void SampleMethod<T>() { }
class GenericList2<T>
//No warning
void SampleMethod<U>() { }
C#
void SwapIfGreater<T>(ref T lhs, ref T rhs) where T : System.IComparable<T>
T temp;
if (lhs.CompareTo(rhs) > 0)
temp = lhs;
lhs = rhs;
rhs = temp;
C#
void DoWork() { }
void DoWork<T>() { }
Vea también
System.Collections.Generic
Guía de programación de C#
Introducción a los genéricos
Métodos
Genéricos y matrices (Guía de
programación de C#)
Artículo • 10/02/2023 • Tiempo de lectura: 2 minutos
C#
class Program
int[] arr = { 0, 1, 2, 3, 4 };
list.Add(x);
ProcessItems<int>(arr);
ProcessItems<int>(list);
// IsReadOnly returns True for the array and False for the List.
System.Console.WriteLine
coll.IsReadOnly);
//coll.RemoveAt(4);
System.Console.WriteLine();
Consulte también
System.Collections.Generic
Guía de programación de C#
Genéricos
Matrices
Genéricos
Delegados genéricos (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Un delegado puede definir sus propios parámetros de tipo. El código que hace
referencia al delegado genérico puede especificar el tipo de argumento para crear un
tipo construido abierto, igual que al crear una instancia de una clase genérica o al llamar
a un método genérico, como se muestra en el siguiente ejemplo:
C#
C#
Del<int> m2 = Notify;
Los delegados definidos dentro de una clase genérica pueden usar los parámetros de
tipo de la clase genérica de la misma manera que lo hacen los métodos de clase.
C#
class Stack<T>
T[] items;
int index;
C#
private static void DoWork(float[] items) { }
Stack<float>.StackDelegate d = DoWork;
Los delegados genéricos son especialmente útiles para definir eventos basados en el
patrón de diseño habitual porque el argumento del remitente puede estar fuertemente
tipado y ya no tiene que convertirse a y de Object.
C#
class Stack<T>
StackEvent(this, a);
class SampleClass
s.StackEvent += o.HandleStackChange;
Consulte también
System.Collections.Generic
Guía de programación de C#
Introducción a los genéricos
Métodos genéricos
Clases genéricas
Interfaces genéricas
Delegados
Genéricos
Diferencias entre plantillas de C++ y
tipos genéricos de C# (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Los tipos genéricos de C# y las plantillas de C++ son dos características de lenguaje que
ofrecen compatibilidad con tipos parametrizados. Pero existen muchas diferencias entre
ambos. En el nivel de sintaxis, los tipos genéricos de C# resultan un enfoque más
sencillo que los tipos parametrizados sin la complejidad de las plantillas de C++.
Además, C# no intenta ofrecer toda la funcionalidad que ofrecen las plantillas de C++.
En el nivel de implementación, la principal diferencia es que las sustituciones de tipo
genérico de C# se realizan en tiempo de ejecución y, por tanto, se conserva la
información de tipo genérico para los objetos con instancias. Para obtener más
información, vea Genéricos en el motor en tiempo de ejecución.
Estas son las principales diferencias entre plantillas de C++ y tipos genéricos de C#:
Los tipos genéricos de C# no ofrecen tanta flexibilidad como las plantillas de C++.
Por ejemplo, no es posible llamar a operadores aritméticos en una clase de tipos
genéricos de C#, aunque es posible llamar a operadores definidos por el usuario.
C# no permite que el parámetro de tipo se use como clase base del tipo genérico.
C++ permite código que podría no ser válido para todos los parámetros de tipo
en la plantilla, que luego busca el tipo específico que se usa como parámetro de
tipo. C# requiere que el código de una clase se escriba de manera que funcione
con cualquier tipo que cumpla las restricciones. Por ejemplo, en C++ es posible
escribir una función que use los operadores aritméticos + y - en objetos del
parámetro de tipo, lo que producirá un error en el momento de creación de
instancias de la plantilla con un tipo que no admita estos operadores. C# no
permite esto; las únicas construcciones de lenguaje permitidas son las que se
pueden deducir de las restricciones.
Consulte también
Guía de programación de C#
Introducción a los genéricos
Templates (Plantillas [C++])
Genéricos en el motor en tiempo de
ejecución (Guía de programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
Cuando se construye por primera vez un tipo genérico con un tipo de valor como
parámetro, el motor de ejecución crea un tipo genérico especializado sustituyendo el
parámetro o los parámetros proporcionados en los lugares adecuados del MSIL. Los
tipos genéricos especializados se crean una vez para cada tipo de valor único que se usa
como parámetro.
Por ejemplo, suponga que el código de su programa ha declarado una pila compuesta
por enteros:
C#
Stack<int> stack;
C#
En cambio, suponga que otra clase Stack<T> con un tipo de valor diferente, como un
long o una estructura definida por el usuario como parámetro, se crea en otro punto
del código. Como resultado, el motor de ejecución genera otra versión del tipo genérico
y sustituye un long en los lugares apropiados en el MSIL. Las conversiones ya no son
necesarias porque cada clase genérica especializada contiene de forma nativa el tipo de
valor.
Los genéricos funcionan de forma ligeramente distinta para los tipos de referencia.
Cuando se construye un tipo genérico por primera vez con un tipo de referencia, el
motor en tiempo de ejecución crea un tipo genérico especializado sustituyendo las
referencias a objetos para los parámetros del MSIL. Después, cada vez que se crea una
instancia de un tipo construido con un tipo de referencia como parámetro,
independientemente del tipo que sea, el motor de ejecución vuelve a usar la versión
especializada del tipo genérico previamente creada. Esto es posible porque todas las
referencias son del mismo tamaño.
Por ejemplo, suponga que tiene dos tipos de referencia, una clase Customer y una clase
Order , y que ha creado una pila de tipos Customer :
C#
class Customer { }
class Order { }
C#
Stack<Customer> customers;
C#
A diferencia de lo que sucede con los tipos de valor, no se crea otra versión
especializada de la clase Stack<T> para el tipo Order . En su lugar, se crea una instancia
de la versión especializada de la clase Stack<T> y se establece la variable orders para
hacer referencia a ella. Suponga que encuentra una línea de código para crear una pila
de tipo Customer :
C#
Como con el uso anterior de la clase Stack<T> creada usando el tipo Order , se crea otra
instancia de la clase especializada Stack<T>. Los punteros contenidos allí se establecen
para hacer referencia a un área de memoria del tamaño de un tipo Customer . Dado que
el número de tipos de referencia puede variar significativamente de un programa a otro,
la implementación de genéricos de C# reduce significativamente la cantidad de código
limitando a uno el número de clases especializadas creadas por el compilador para las
clases genéricas de tipos de referencia.
Vea también
System.Collections.Generic
Guía de programación de C#
Introducción a los genéricos
Genéricos
Genéricos y reflexión (Guía de
programación de C#)
Artículo • 10/02/2023 • Tiempo de lectura: 3 minutos
Dado que Common Language Runtime (CLR) tiene acceso a la información de tipos
genéricos en tiempo de ejecución, se puede usar la reflexión para obtener información
sobre los tipos genéricos de la misma manera que para los tipos no genéricos. Para
obtener más información, vea Genéricos en el motor en tiempo de ejecución.
Para obtener una lista de las condiciones invariables para los términos usados en la
reflexión genérica, vea los comentarios de la propiedad IsGenericType.
Vea también
Guía de programación de C#
Genéricos
Reflexión y tipos genéricos
Genéricos
Genéricos y atributos (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Los atributos pueden aplicarse a los tipos genéricos de la misma manera que los tipos
no genéricos. Para obtener más información sobre la aplicación de los atributos, vea
Atributos.
Los atributos personalizados solo se permiten para hacer referencia a tipos genéricos
abiertos, que son tipos genéricos para los que no se proporciona ningún argumento de
tipo, y tipos genéricos construidos cerrados, que proporcionan argumentos para todos
los parámetros de tipo.
C#
C#
[CustomAttribute(info = typeof(GenericClass1<>))]
class ClassA { }
C#
[CustomAttribute(info = typeof(GenericClass2<,>))]
class ClassB { }
class ClassC { }
C#
class ClassD<T> { }
C#
Consulte también
Guía de programación de C#
Genéricos
Atributos
Sistema de archivos y el Registro (Guía
de programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
En los artículos siguientes se muestra cómo usar C# y .NET para realizar diversas
operaciones básicas en los archivos, las carpetas y el Registro.
En esta sección
Título Descripción
Procedimiento para recorrer en iteración Muestra cómo realizar una iteración manual a través
un árbol de directorio de un árbol de directorio.
Procedimiento para obtener información Muestra cómo recuperar información como las horas
sobre archivos, carpetas y unidades de creación y el tamaño, así como sobre archivos,
carpetas y unidades.
Procedimiento para crear archivos o Muestra cómo crear un archivo o una carpeta
carpetas nuevos.
Procedimiento para copiar, eliminar y Muestra cómo copiar, eliminar y mover archivos y
mover archivos y carpetas (Guía de carpetas.
programación de C#)
Procedimiento para leer un archivo de Muestra cómo recuperar el texto de un archivo línea
texto línea a línea a línea.
Procedimiento para crear una clave en el Muestra cómo escribir una clave en el registro del
Registro sistema.
Secciones relacionadas
E/S de archivos y secuencias
Procedimiento para copiar, eliminar y mover archivos y carpetas (Guía de
programación de C#)
Guía de programación de C#
System.IO
Procedimiento Recorrer en iteración un
árbol de directorio (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 7 minutos
System.IO.FileInfo o System.IO.DirectoryInfo.
7 Nota
En el caso más simple, en el que sabe con seguridad que tiene permisos de acceso para
todos los directorios incluidos en una raíz especificada, puede usar la marca
System.IO.SearchOption.AllDirectories . Esta marca devuelve todos los subdirectorios
anidados que coinciden con el patrón especificado. En el ejemplo siguiente se muestra
cómo usar esta marca.
C#
root.GetDirectories("*.*", System.IO.SearchOption.AllDirectories);
El punto débil de este enfoque es que si uno de los subdirectorios incluidos en la raíz
especificada produce una excepción DirectoryNotFoundException o
UnauthorizedAccessException, se produce un error en todo el método y no devuelve
ningún directorio. Sucede lo mismo cuando usa el método GetFiles. Si tiene que
controlar estas excepciones en subcarpetas específicas, debe recorrer manualmente el
árbol de directorios, como se muestra en los ejemplos siguientes.
Si tiene que realizar diversas operaciones en los archivos y las carpetas, puede dividir
estos ejemplos en partes mediante la refactorización de la operación en funciones
separadas que se puedan invocar usando un solo delegado.
7 Nota
Ejemplos
En el ejemplo siguiente se muestra cómo recorrer un árbol de directorios mediante
recursividad. El enfoque recursivo resulta elegante, pero puede producir una excepción
de desbordamiento de la pila si el árbol de directorios es grande y cuenta con muchos
elementos anidados.
Las excepciones concretas que se controlan y las acciones determinadas que se realizan
en cada archivo o carpeta se proporcionan simplemente como ejemplos. Debe
modificar este código para que se ajuste a sus requisitos concretos. Para obtener más
información, vea los comentarios del código.
C#
public class RecursiveFileSearch
if (!di.IsReady)
continue;
WalkDirectoryTree(rootDir);
Console.WriteLine(s);
}
Console.ReadKey();
try
files = root.GetFiles("*.*");
catch (UnauthorizedAccessException e)
// can try to elevate your privileges and access the file again.
log.Add(e.Message);
catch (System.IO.DirectoryNotFoundException e)
Console.WriteLine(e.Message);
if (files != null)
Console.WriteLine(fi.FullName);
subDirs = root.GetDirectories();
WalkDirectoryTree(dirInfo);
En el ejemplo siguiente se muestra cómo recorrer en iteración los archivos y las carpetas
de un árbol de directorios sin usar la recursividad. Esta técnica usa el tipo de colección
genérica Stack<T>, que es una pila de tipo LIFO (último en entrar, primero en salir).
Las excepciones concretas que se controlan y las acciones determinadas que se realizan
en cada archivo o carpeta se proporcionan simplemente como ejemplos. Debe
modificar este código para que se ajuste a sus requisitos concretos. Para obtener más
información, vea los comentarios del código.
C#
TraverseTree(args[0]);
Console.ReadKey();
if (!System.IO.Directory.Exists(root))
dirs.Push(root);
string[] subDirs;
try
subDirs = System.IO.Directory.GetDirectories(currentDir);
// you are intending to perform and also on how much you know
with certainty
catch (UnauthorizedAccessException e)
Console.WriteLine(e.Message);
continue;
catch (System.IO.DirectoryNotFoundException e)
Console.WriteLine(e.Message);
continue;
try
files = System.IO.Directory.GetFiles(currentDir);
catch (UnauthorizedAccessException e)
Console.WriteLine(e.Message);
continue;
catch (System.IO.DirectoryNotFoundException e)
Console.WriteLine(e.Message);
continue;
try
catch (System.IO.FileNotFoundException e)
Console.WriteLine(e.Message);
continue;
dirs.Push(str);
Programación sólida
Un código eficaz de iteración de archivos debe tener en cuenta las numerosas
dificultades del sistema de archivos. Para más información sobre el sistema de archivos
de Windows, vea NTFS overview (Introducción a NTFS).
Vea también
System.IO
LINQ y directorios de archivos
Registro y sistema de archivos (Guía de programación de C#)
Procedimiento para obtener
información sobre archivos, carpetas y
unidades (Guía de programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
En .NET, puede acceder a información del sistema de archivos mediante las clases
siguientes:
System.IO.FileInfo
System.IO.DirectoryInfo
System.IO.DriveInfo
System.IO.Directory
System.IO.File
C#
Ejemplo
En el ejemplo siguiente se muestran diversas maneras de obtener acceso a información
sobre archivos y carpetas.
C#
class FileSysInfo
Console.WriteLine(di.TotalFreeSpace);
Console.WriteLine(di.VolumeLabel);
// Get the root directory and print out some information about it.
Console.WriteLine(dirInfo.Attributes.ToString());
// Get the files in the directory and print out some information
about them.
Console.WriteLine(d.Name);
Console.WriteLine(currentDirName);
// Use this method when storage space is an issue, and when you
might
// hold on to the file name reference for a while before you try to
access
// the file.
System.IO.FileInfo fi = null;
try
fi = new System.IO.FileInfo(s);
catch (System.IO.FileNotFoundException e)
Console.WriteLine(e.Message);
continue;
if (!System.IO.Directory.Exists(@"C:\Users\Public\TestFolder\"))
System.IO.Directory.CreateDirectory(@"C:\Users\Public\TestFolder\");
System.IO.Directory.SetCurrentDirectory(@"C:\Users\Public\TestFolder\");
currentDirName = System.IO.Directory.GetCurrentDirectory();
Console.WriteLine(currentDirName);
Console.ReadKey();
Programación sólida
Al procesar cadenas de ruta de acceso especificadas por el usuario, también debe
controlar las excepciones para las condiciones siguientes:
Vea también
System.IO
Guía de programación de C#
Registro y sistema de archivos (Guía de programación de C#)
Procedimiento Crear archivos o carpetas
(Guía de programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
Puede crear una carpeta en el equipo mediante programación, crear una subcarpeta,
crear un archivo en la subcarpeta y escribir datos en el archivo.
Ejemplo
C#
// You can write out the path name directly instead of using the
Combine
// You can extend the depth of your path if you want to.
// Create the subfolder. You can verify in File Explorer that you
have this
// Top-Level Folder
// SubFolder
System.IO.Directory.CreateDirectory(pathString);
// This example uses a random string for the name, but you also can
specify
// a particular name.
if (!System.IO.File.Exists(pathString))
using (System.IO.FileStream fs =
System.IO.File.Create(pathString))
fs.WriteByte(i);
else
return;
try
Console.WriteLine();
catch (System.IO.IOException e)
Console.WriteLine(e.Message);
System.Console.ReadKey();
}
// Sample output:
//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
27 28 29
//30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
53 54 55 56
// 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
80 81 82 8
//3 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
C#
C#
fs.WriteByte(i);
Ejecute el ejemplo varias veces para comprobar que los datos se agreguen al archivo
cada vez.
Para obtener más valores FileMode que puede probar, consulte FileMode.
Seguridad de .NET
En los casos de confiabilidad parcial, es posible que se devuelva una instancia de la clase
SecurityException.
Si no tiene permiso para crear la carpeta, el ejemplo devuelve una instancia de la clase
UnauthorizedAccessException.
Vea también
System.IO
Guía de programación de C#
Registro y sistema de archivos (Guía de programación de C#)
Procedimiento Copiar, eliminar y mover
archivos y carpetas (Guía de
programación de C#)
Artículo • 10/02/2023 • Tiempo de lectura: 3 minutos
En los siguientes ejemplos se muestra cómo copiar, mover y eliminar archivos y carpetas
de una manera sincrónica con las clases System.IO.File, System.IO.Directory,
System.IO.FileInfo y System.IO.DirectoryInfo desde el espacio de nombres System.IO. En
estos ejemplos no se proporciona una barra de progreso ni ninguna otra interfaz de
usuario. Si quiere proporcionar un cuadro de diálogo de progreso estándar, consulte
Procedimiento Proporcionar un cuadro de diálogo de progreso para operaciones de
archivos.
Ejemplos
En el ejemplo siguiente se muestra cómo copiar archivos y directorios.
C#
// To run this sample, first create the following directories and files:
// C:\Users\Public\TestFolder
// C:\Users\Public\TestFolder\test.txt
// C:\Users\Public\TestFolder\SubDir\test.txt
System.IO.Directory.CreateDirectory(targetPath);
if (System.IO.Directory.Exists(sourcePath))
fileName = System.IO.Path.GetFileName(s);
else
Console.ReadKey();
C#
System.IO.File.Move(sourceFile, destinationFile);
System.IO.Directory.Move(@"C:\Users\Public\public\test\",
@"C:\Users\Public\private");
C#
// C:\Users\Public\DeleteTest\test1.txt
// C:\Users\Public\DeleteTest\test2.txt
// C:\Users\Public\DeleteTest\SubDir\test2.txt
if(System.IO.File.Exists(@"C:\Users\Public\DeleteTest\test.txt"))
try
System.IO.File.Delete(@"C:\Users\Public\DeleteTest\test.txt");
catch (System.IO.IOException e)
Console.WriteLine(e.Message);
return;
System.IO.FileInfo fi = new
System.IO.FileInfo(@"C:\Users\Public\DeleteTest\test2.txt");
try
fi.Delete();
catch (System.IO.IOException e)
Console.WriteLine(e.Message);
try
System.IO.Directory.Delete(@"C:\Users\Public\DeleteTest");
catch (System.IO.IOException e)
Console.WriteLine(e.Message);
if(System.IO.Directory.Exists(@"C:\Users\Public\DeleteTest"))
try
System.IO.Directory.Delete(@"C:\Users\Public\DeleteTest",
true);
catch (System.IO.IOException e)
Console.WriteLine(e.Message);
System.IO.DirectoryInfo di = new
System.IO.DirectoryInfo(@"C:\Users\Public\public");
try
di.Delete(true);
catch (System.IO.IOException e)
Console.WriteLine(e.Message);
Consulte también
System.IO
Guía de programación de C#
Registro y sistema de archivos (Guía de programación de C#)
Procedimiento para proporcionar un cuadro de diálogo de progreso para
operaciones de archivos
E/S de archivos y secuencias
Tareas de E/S comunes
Procedimiento Proporcionar un cuadro
de diálogo de progreso para
operaciones de archivos (Guía de
programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
7 Nota
Ejemplo
El siguiente código copia el directorio que especifica sourcePath en el directorio que
especifica destinationPath . Este código también proporciona un cuadro de diálogo
estándar en el que se muestra el tiempo estimado restante antes de que finalice la
operación.
C#
// The following using directive requires a project reference to
Microsoft.VisualBasic.
using Microsoft.VisualBasic.FileIO;
class FileProgress
// Specify the path to a folder that you want to copy. If the folder
is small,
FileSystem.CopyDirectory(sourcePath, destinationPath,
UIOption.AllDialogs);
}
Consulte también
Registro y sistema de archivos (Guía de programación de C#)
Procedimiento Escribir en un archivo de
texto (Guía de programación de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
En este artículo hay varios ejemplos en los que muestran distintas formas de escribir
texto en un archivo. En los dos primeros se usan métodos estáticos útiles de la clase
System.IO.File para escribir cada elemento de cualquier interfaz IEnumerable<string> y
un elemento string en un archivo de texto. En el tercer ejemplo se muestra cómo
agregar texto a un archivo cuando hay que procesar cada línea individualmente a
medida que se escribe en el archivo. En los tres primeros ejemplos se sobrescribe todo
el contenido existente del archivo. En el último ejemplo se muestra cómo anexar texto a
un archivo existente.
Todos estos ejemplos escriben literales de cadena en los archivos. Si quiere aplicar
formato al texto escrito en un archivo, use el método Format o la característica
interpolación de cadenas de C#.
class WriteAllLines
string[] lines =
};
class WriteAllText
string text =
"A class is the most powerful data type in C#. Like a structure,
" +
"a class defines the data and behavior of the data type. ";
Se crea una instancia de una cadena en función del literal de cadena asignado.
class StreamWriterOne
if (!line.Contains("Second"))
await file.WriteLineAsync(line);
class StreamWriterTwo
Excepciones
Las condiciones siguientes pueden provocar una excepción:
Vea también
Guía de programación de C#
Registro y sistema de archivos (Guía de programación de C#)
Procedimiento Leer de un archivo de
texto (Guía de programación de C#)
Artículo • 10/02/2023 • Tiempo de lectura: 2 minutos
En este ejemplo se lee el contenido de un archivo de texto con los métodos estáticos
ReadAllText y ReadAllLines de la clase System.IO.File.
7 Nota
Ejemplo
C#
class ReadFromFile
// How to: Write to a Text File. You can change the path and
// Example #1
string text =
System.IO.File.ReadAllText(@"C:\Users\Public\TestFolder\WriteText.txt");
// Example #2
// Read each line of the file into a string array. Each element
string[] lines =
System.IO.File.ReadAllLines(@"C:\Users\Public\TestFolder\WriteLines2.txt");
Console.WriteLine("\t" + line);
System.Console.ReadKey();
}
Compilar el código
Copie el código y péguelo en una aplicación de consola de C#.
Programación sólida
Las condiciones siguientes pueden provocar una excepción:
Seguridad de .NET
No confíe en el nombre de un archivo para determinar el contenido del archivo. Por
ejemplo, el archivo myFile.cs puede que no sea un archivo de código fuente de C#.
Vea también
System.IO
Guía de programación de C#
Registro y sistema de archivos (Guía de programación de C#)
Procedimiento para leer un archivo de
texto de línea en línea (guía de
programación de C#)
Artículo • 04/01/2023 • Tiempo de lectura: 2 minutos
En este ejemplo se lee el contenido de un archivo de texto línea a línea en una cadena
mediante el método ReadLines de la clase File . Cada línea de texto se almacena en la
cadena line y se muestra en la pantalla.
Ejemplo
C#
int counter = 0;
System.Console.WriteLine(line);
counter++;
System.Console.ReadLine();
Compilar el código
Copie el código y péguelo en el método Main de una aplicación de consola.
Programación sólida
Las condiciones siguientes pueden provocar una excepción:
Seguridad de .NET
No tome ninguna decisión sobre el contenido del archivo basándose en su nombre. Por
ejemplo, es posible que el archivo myFile.cs no sea un archivo de código fuente de C#.
Vea también
System.IO
Guía de programación de C#
Registro y sistema de archivos (Guía de programación de C#)
Procedimiento para crear una clave del
Registro (guía de programación de C#)
Artículo • 28/11/2022 • Tiempo de lectura: 2 minutos
En este ejemplo se agrega el par de valores "Name" e "Isabella" al Registro del usuario
actual en la clave "Names".
Ejemplo
C#
Microsoft.Win32.RegistryKey key;
key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey("Names");
key.SetValue("Name", "Isabella");
key.Close();
Compilar el código
Copie el código y péguelo en el método Main de una aplicación de consola.
Sustituya el parámetro Names por el nombre de una clave que exista directamente
en el nodo HKEY_CURRENT_USER del Registro.
Programación sólida
Examine la estructura del Registro para buscar una ubicación adecuada para la clave. Por
ejemplo, es posible que quiera abrir la clave Software del usuario actual y crear una
clave con el nombre de la empresa. Luego agregue los valores del Registro a la clave de
la empresa.
Seguridad de .NET
Es más seguro escribir datos en la carpeta de usuario
( Microsoft.Win32.Registry.CurrentUser ) que en el equipo local
( Microsoft.Win32.Registry.LocalMachine ).
Cuando se crea un valor del Registro, se debe decidir qué hacer si ese valor ya existe.
Puede que otro proceso, quizás uno malintencionado, ya haya creado el valor y tenga
acceso a él. Al colocar datos en el valor del Registro, estos están a disposición del otro
proceso. Para evitarlo, use el método Método
Overload:Microsoft.Win32.RegistryKey.GetValue . Si la clave aún no existe, devuelve null.
Vea también
System.IO
Guía de programación de C#
Registro y sistema de archivos (Guía de programación de C#)
Read, write and delete from the registry with C# (Leer, escribir y eliminar en el
Registro con C#)
Interoperability Overview
Artículo • 25/02/2023 • Tiempo de lectura: 3 minutos
.NET enables interoperability with unmanaged code through platform invoke services,
the System.Runtime.InteropServices namespace, C++ interoperability, and COM
interoperability (COM interop).
Platform Invoke
Platform invoke is a service that enables managed code to call unmanaged functions
implemented in dynamic link libraries (DLLs), such as the Microsoft Windows API. It
locates and invokes an exported function and marshals its arguments (integers, strings,
arrays, structures, and so on) across the interoperation boundary as needed.
For more information, see Consuming Unmanaged DLL Functions and How to use
platform invoke to play a WAV file.
7 Nota
C++ Interop
You can use C++ interop, also known as It Just Works (IJW), to wrap a native C++ class.
C++ interop enables code authored in C# or another .NET language to access it. You
write C++ code to wrap a native DLL or COM component. Unlike other .NET languages,
Visual C++ has interoperability support that enables managed and unmanaged code in
the same application and even in the same file. You then build the C++ code by using
the /clr compiler switch to produce a managed assembly. Finally, you add a reference to
the assembly in your C# project and use the wrapped objects just as you would use
other managed classes.
1. Locate a COM component to use and register it. Use regsvr32.exe to register or
un–register a COM DLL.
2. Add to the project a reference to the COM component or type library.
When you
add the reference, Visual Studio uses the Tlbimp.exe (Type Library Importer), which
takes a type library as input, to output a .NET interop assembly. The assembly, also
named a runtime callable wrapper (RCW), contains managed classes and interfaces
that wrap the COM classes and interfaces that are in the type library. Visual Studio
adds to the project a reference to the generated assembly.
3. Create an instance of a class defined in the RCW. Creating an instance of that class
creates an instance of the COM object.
4. Use the object just as you use other managed objects. When the object is
reclaimed by garbage collection, the instance of the COM object is also released
from memory.
For more information, see Exposing COM Components to the .NET Framework.
Exposing C# to COM
COM clients can consume C# types that have been correctly exposed. The basic steps to
expose C# types are as follows:
For more information, see Exposing .NET Framework Components to COM and Example
COM Class.
See also
Improving Interop Performance
Introduction to Interoperability between COM and .NET
Introduction to COM Interop in Visual Basic
Marshaling between Managed and Unmanaged Code
Interoperating with Unmanaged Code
Interoperability Overview
Artículo • 25/02/2023 • Tiempo de lectura: 3 minutos
.NET enables interoperability with unmanaged code through platform invoke services,
the System.Runtime.InteropServices namespace, C++ interoperability, and COM
interoperability (COM interop).
Platform Invoke
Platform invoke is a service that enables managed code to call unmanaged functions
implemented in dynamic link libraries (DLLs), such as the Microsoft Windows API. It
locates and invokes an exported function and marshals its arguments (integers, strings,
arrays, structures, and so on) across the interoperation boundary as needed.
For more information, see Consuming Unmanaged DLL Functions and How to use
platform invoke to play a WAV file.
7 Nota
C++ Interop
You can use C++ interop, also known as It Just Works (IJW), to wrap a native C++ class.
C++ interop enables code authored in C# or another .NET language to access it. You
write C++ code to wrap a native DLL or COM component. Unlike other .NET languages,
Visual C++ has interoperability support that enables managed and unmanaged code in
the same application and even in the same file. You then build the C++ code by using
the /clr compiler switch to produce a managed assembly. Finally, you add a reference to
the assembly in your C# project and use the wrapped objects just as you would use
other managed classes.
1. Locate a COM component to use and register it. Use regsvr32.exe to register or
un–register a COM DLL.
2. Add to the project a reference to the COM component or type library.
When you
add the reference, Visual Studio uses the Tlbimp.exe (Type Library Importer), which
takes a type library as input, to output a .NET interop assembly. The assembly, also
named a runtime callable wrapper (RCW), contains managed classes and interfaces
that wrap the COM classes and interfaces that are in the type library. Visual Studio
adds to the project a reference to the generated assembly.
3. Create an instance of a class defined in the RCW. Creating an instance of that class
creates an instance of the COM object.
4. Use the object just as you use other managed objects. When the object is
reclaimed by garbage collection, the instance of the COM object is also released
from memory.
For more information, see Exposing COM Components to the .NET Framework.
Exposing C# to COM
COM clients can consume C# types that have been correctly exposed. The basic steps to
expose C# types are as follows:
For more information, see Exposing .NET Framework Components to COM and Example
COM Class.
See also
Improving Interop Performance
Introduction to Interoperability between COM and .NET
Introduction to COM Interop in Visual Basic
Marshaling between Managed and Unmanaged Code
Interoperating with Unmanaged Code
Cómo acceder a objetos de
interoperabilidad de Office
Artículo • 09/03/2023 • Tiempo de lectura: 11 minutos
En este artículo se utilizarán las nuevas características para escribir código que crea y
muestra una hoja de cálculo de Microsoft Office Excel. Se escribe código para agregar
un documento de Office Word que contiene un icono que está vinculado a la hoja de
cálculo de Excel.
Para completar este tutorial, es necesario tener Microsoft Office Excel 2007 y Microsoft
Office Word 2007 —o una versión posterior— instalados en el equipo.
7 Nota
) Importante
C#
C#
Agregue el código siguiente al método Main para crear una lista bankAccounts lista que
contenga dos cuentas.
C#
new Account {
ID = 345678,
Balance = 541.27
},
new Account {
ID = 1230221,
Balance = -127.44
};
C#
excelApp.Visible = true;
excelApp.Workbooks.Add();
C#
C#
var row = 1;
row++;
C#
workSheet.Columns[1].AutoFit();
workSheet.Columns[2].AutoFit();
C#
((Excel.Range)workSheet.Columns[1]).AutoFit();
((Excel.Range)workSheet.Columns[2]).AutoFit();
C#
DisplayInExcel(bankAccounts);
Presione CTRL+F5. Aparece una hoja de cálculo de Excel que contiene los datos de las
dos cuentas.
C#
wordApp.Visible = true;
// The Add method has four reference parameters, all of which are
wordApp.Documents.Add();
C#
// the spreadsheet.
CreateIconInWordDoc();
C#
// Put the spreadsheet contents on the clipboard. The Copy method has one
workSheet.Range["A1:B3"].Copy();
Presione CTRL+F5. Un documento de Word aparecerá con un icono. Haga doble clic en
el icono para abrir la hoja de cálculo en primer plano.
Además, la programación es más fácil porque el tipo dynamic representa los tipos
requeridos y devueltos que se declaran en los métodos COM. Las variables de tipo
dynamic no se evalúan hasta el tiempo de ejecución, lo que elimina la necesidad de la
conversión explícita. Para obtener más información, vea Uso del tipo dynamic.
C#
((Excel.Range)workSheet.Columns[1]).AutoFit();
((Excel.Range)workSheet.Columns[2]).AutoFit();
C#
workSheet.Range["A1", "B3"].AutoFormat(
Excel.XlRangeAutoFormat.xlRangeAutoFormatClassic2);
El método AutoFormat tiene siete parámetros de valor, todos ellos opcionales. Los
argumentos con nombre y los argumentos opcionales permiten proporcionar
argumentos para ninguno, algunos o todos ellos. En la instrucción anterior, se
proporciona un argumento para uno solo de los parámetros, Format . Puesto que
Format es el primer parámetro de la lista correspondiente, no es necesario proporcionar
el nombre de parámetro. Sin embargo, la instrucción puede ser más fácil de entender si
se incluye el nombre del parámetro, como se muestra en el código siguiente.
C#
workSheet.Range["A1", "B3"].AutoFormat(Format:
Excel.XlRangeAutoFormat.xlRangeAutoFormatClassic2);
Presione CTRL+F5 para ver el resultado. Puede encontrar otros formatos incluidos en la
enumeración XlRangeAutoFormat.
Ejemplo
En el código siguiente se muestra el ejemplo completo.
C#
using System.Collections.Generic;
using Excel = Microsoft.Office.Interop.Excel;
namespace OfficeProgramminWalkthruComplete
class Walkthrough
new Account {
ID = 345678,
Balance = 541.27
},
new Account {
ID = 1230221,
Balance = -127.44
};
DisplayInExcel(bankAccounts);
// the spreadsheet.
CreateIconInWordDoc();
excelApp.Visible = true;
excelApp.Workbooks.Add();
//Excel._Worksheet workSheet =
(Excel.Worksheet)excelApp.ActiveSheet;
var row = 1;
row++;
workSheet.Columns[1].AutoFit();
workSheet.Columns[2].AutoFit();
workSheet.Range["A1", "B3"].AutoFormat(
Excel.XlRangeAutoFormat.xlRangeAutoFormatClassic2);
workSheet.Range["A1:B3"].Copy();
wordApp.Visible = true;
wordApp.Documents.Add();
Consulte también
Type.Missing
dynamic
Argumentos opcionales y con nombre
Procedimiento para usar argumentos opcionales y con nombre en la programación
de Office
Procedimiento para usar propiedades
indizadas en la programación de
interoperabilidad COM
Artículo • 09/03/2023 • Tiempo de lectura: 2 minutos
Las propiedades indexadas funcionan junto con otras características de C#, como los
argumentos con nombre y opcionales, un nuevo tipo (dinámico) y la información de
tipo incrustada, para mejorar la programación en Microsoft Office.
) Importante
En versiones anteriores de C#, los métodos son solo accesibles como propiedades si el
método get no tiene ningún parámetro y el método set tiene solo un parámetro de
valor. En cambio, no todas las propiedades COM cumplen esas restricciones. Por
ejemplo, la propiedad Range[] de Excel tiene un descriptor de acceso get que requiere
un parámetro para el nombre del intervalo. Antes, como no había acceso directo a la
propiedad Range , había que usar el método get_Range en su lugar, como se muestra en
el ejemplo siguiente.
{language}
// . . .
// Visual C# 2010.
// . . .
{language}
// Visual C# 2010.
targetRange.Value = "Name";
Ejemplo
En el código siguiente se muestra un ejemplo completo. Para más información sobre
cómo preparar un proyecto con acceso a la API de Office, consulte Procedimiento Tener
acceso a objetos de interoperabilidad de Office mediante las características de Visual C#
(Guía de programación de C#).
{language}
// this example.
using System;
namespace IndexedProperties
class Program
CSharp2010();
excelApp.Workbooks.Add();
excelApp.Visible = true;
targetRange.Value = "Name";
excelApp.Workbooks.Add(Type.Missing);
excelApp.Visible = true;
targetRange.set_Value(Type.Missing, "Name");
// Or
//targetRange.Value2 = "Name";
Vea también
Argumentos opcionales y con nombre
dynamic
Uso de tipo dinámico
Procedimiento para usar la invocación
de plataforma para reproducir un
archivo WAV
Artículo • 03/03/2023 • Tiempo de lectura: 2 minutos
Ejemplo
En este código de ejemplo se usa DllImportAttribute para importar el punto de entrada
del método winmm.dll de PlaySound como Form1 PlaySound() . El ejemplo tiene un
formulario Windows Forms simple con un botón. Al hacer clic en el botón, se abre un
cuadro de diálogo OpenFileDialog estándar de Windows para que pueda abrir un
archivo y reproducirlo. Cuando se selecciona un archivo de onda, se reproduce
mediante el método PlaySound() de la biblioteca winmm.dll. Para obtener más
información sobre este método, vea Using the PlaySound function with Waveform-
Audio Files (Uso de la función PlaySound con archivos para forma de onda de sonido).
Busque y seleccione un archivo que tenga una extensión .wav y, después, seleccione
Abrir para reproducirlo mediante la invocación de plataforma. Un cuadro de texto
muestra la ruta de acceso completa del archivo seleccionado.
C#
using System.Runtime.InteropServices;
namespace WinSound;
InitializeComponent();
[System.Flags]
SND_SYNC = 0x0000,
SND_ASYNC = 0x0001,
SND_NODEFAULT = 0x0002,
SND_LOOP = 0x0008,
SND_NOSTOP = 0x0010,
SND_NOWAIT = 0x00002000,
SND_FILENAME = 0x00020000,
SND_RESOURCE = 0x00040004
}
dialog1.InitialDirectory = @"c:\";
//<Snippet5>
//</Snippet5>
dialog1.FilterIndex = 2;
dialog1.RestoreDirectory = true;
if (dialog1.ShowDialog() == DialogResult.OK)
textBox1.Text = dialog1.FileName;
// when users click on the form, generates code that looks for a
default method
El cuadro de diálogo Abrir archivos se puede filtrar con los valores correspondientes
para mostrar solo los archivos que tengan la extensión .wav.
C#
this.SuspendLayout();
//
// button1
//
this.button1.Name = "button1";
this.button1.TabIndex = 0;
this.button1.Text = "Browse";
//
// textBox1
//
this.textBox1.Name = "textBox1";
this.textBox1.TabIndex = 1;
//
// Form1
//
this.Controls.Add(this.textBox1);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.ResumeLayout(false);
this.PerformLayout();
Consulte también
Aproximación a la invocación de plataforma
Serialización de datos con invocación de plataforma
Tutorial: Programación de Office en C#
Artículo • 09/03/2023 • Tiempo de lectura: 10 minutos
) Importante
Requisitos previos
Debe tener Microsoft Office Excel y Microsoft Office Word instalados en su equipo para
completar este tutorial.
7 Nota
Es posible que tu equipo muestre nombres o ubicaciones diferentes para algunos
de los elementos de la interfaz de usuario de Visual Studio en las siguientes
instrucciones. La edición de Visual Studio que se tenga y la configuración que se
utilice determinan estos elementos. Para obtener más información, vea
Personalizar el IDE.
Agregar referencias
1. En el Explorador de soluciones, haga clic con el botón derecho en el nombre del
proyecto y luego seleccione Agregar referencia. Aparecerá el cuadro de diálogo
Agregar referencia.
2. En la pestaña Ensamblados, seleccione Microsoft.Office.Interop.Excel, versión
<version>.0.0.0 (para obtener una clave de los números de versión de productos
de Office, vea Versiones de Microsoft ), en la lista Nombre de componente y,
después, mantenga presionada la tecla CTRL y seleccione
Microsoft.Office.Interop.Word, version <version>.0.0.0 . Si no ve los
ensamblados, es posible que tenga que instalarlos (vea Procedimientos para
instalar ensamblados de interoperabilidad primarios de Office).
3. Seleccione Aceptar.
C#
using System.Collections.Generic;
using Excel = Microsoft.Office.Interop.Excel;
C#
class Account
Para crear una lista bankAccounts que contenga dos cuentas, agregue el código
siguiente al método ThisAddIn_Startup en ThisAddIn.cs. Las declaraciones de lista usan
inicializadores de colección.
C#
new Account
ID = 345,
Balance = 541.27
},
new Account
ID = 123,
Balance = -127.44
};
C#
excelApp.Workbooks.Add();
excelApp.Visible = true;
excelApp.Range["A1"].Value = "ID";
excelApp.Range["B1"].Value = "Balance";
excelApp.Range["A2"].Select();
DisplayFunc(ac, excelApp.ActiveCell);
excelApp.ActiveCell.Offset[1, 0].Select();
excelApp.Range["A1:B3"].Copy();
C#
excelApp.Range["A1"].Value = "ID";
excelApp.ActiveCell.Offset[1, 0].Select();
C#
excelApp.Columns[1].AutoFit();
excelApp.Columns[2].AutoFit();
Invocación de DisplayInExcel
Agregue el código siguiente al final del método ThisAddIn_StartUp . La llamada a
DisplayInExcel contiene dos argumentos. El primer argumento es el nombre de la lista
de cuentas procesadas. El segundo argumento es una expresión lambda de varias líneas
que define cómo procesar los datos. Los valores ID y balance de cada cuenta se
muestran en las celdas adyacentes y la fila se muestra en rojo si el saldo es inferior a
cero. Para obtener más información, vea Expresiones lambda.
C#
cell.Value = account.ID;
if (account.Balance < 0)
cell.Interior.Color = 255;
});
Presione F5 para ejecutar el programa. Aparece una hoja de cálculo de Excel que
contiene los datos de las cuentas.
C#
wordApp.Visible = true;
wordApp.Documents.Add();
Ejecución de la aplicación
Presione F5 para ejecutar la aplicación. Excel se abre y muestra una tabla que contiene la
información de las dos cuentas de bankAccounts . Entonces aparece un documento de
Word que contiene un vínculo a la tabla de Excel.
lista. Dado que importó los tipos que necesita el proyecto en el ensamblado, no es
necesario instalar referencias a un PIA. La importación de los tipos en el
ensamblado facilita la implementación. Los PIA no tienen que estar presentes en el
equipo del usuario. Una aplicación no requiere la implementación de una versión
específica de un PIA. Las aplicaciones pueden funcionar con varias versiones de
Office, siempre que existan las API necesarias en todas las versiones. Dado que la
implementación de los PIA ya no es necesaria, puede crear una aplicación en
escenarios avanzados que funcione con varias versiones de Office, incluidas
versiones anteriores. El código no puede usar ninguna API que no esté disponible
en la versión de Office con la que trabaja. No siempre está claro si una API
determinada estaba disponible en una versión anterior. No se recomienda trabajar
con versiones anteriores de Office.
6. Cierre la ventana del manifiesto y la del ensamblado.
insertados.
7. Haga doble clic en el icono MANIFIESTO y desplácese por la lista de ensamblados
de referencia. Tanto Microsoft.Office.Interop.Word como
Microsoft.Office.Interop.Excel están en la lista. Dado que la aplicación hace
Consulte también
Propiedades autoimplementadas (C#)
Inicializadores de objeto y colección
Argumentos opcionales y con nombre
dynamic
Uso de tipo dinámico
Expresiones lambda (C#)
Tutorial: Inserción de información de tipos de los ensamblados de Microsoft Office
en Visual Studio
Tutorial: Inserción de tipos de ensamblados administrados
Tutorial: Creación del primer complemento VSTO para Excel
Clase COM de ejemplo
Artículo • 03/03/2023 • Tiempo de lectura: 2 minutos
El código siguiente es un ejemplo de una clase que se expondría como un objeto COM.
Una vez que coloque este código en un archivo .cs agregado al proyecto, establezca la
propiedad Registrar para interoperabilidad COM en True. Para obtener más
información, vea Cómo: Registrar un componente para interoperabilidad COM.
Exponer objetos de C# para COM requiere declarar una interfaz de clase, una "interfaz
de eventos" si es necesario y la propia clase. Los miembros de clase deben seguir estas
reglas para que sean visibles en COM:
Los demás miembros públicos de la clase que no declare en estas interfaces no serán
visibles para COM, pero lo serán para otros objetos de .NET. Para exponer propiedades y
métodos en COM, se deben declarar en la interfaz de clase y marcar con el atributo
DispId , e implementarlos en la clase. El orden en que se declaran los miembros en la
interfaz es el orden que se usa para la tabla virtual de COM. Para exponer los eventos de
la clase, se deben declarar en la interfaz de eventos y marcarlos con un atributo DispId .
La clase no debe implementar esta interfaz.
La clase implementa la interfaz de clase y puede implementar más de una interfaz, pero
la primera implementación es la interfaz de clase predeterminada. Implemente los
métodos y propiedades expuestos para COM aquí. Deben ser públicos y coincidir con
las declaraciones de la interfaz de clase. Asimismo, declare los eventos iniciados por la
clase aquí. Deben ser públicos y coincidir con las declaraciones de la interfaz de eventos.
Ejemplo
C#
using System.Runtime.InteropServices;
namespace project_name
[Guid("EAA4976A-45C3-4BC5-BC0B-E474F4C3C83F")]
[Guid("7BD20046-DF8C-44A6-8F6B-687FAA26FA71"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[Guid("0D53A3E8-E51A-49C7-944E-E72A2064F938"),
ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(ComClass1Events))]
Consulte también
Interoperabilidad
Página Compilar (Diseñador de proyectos) (C#)
Referencia de C#
Artículo • 15/02/2023 • Tiempo de lectura: 4 minutos
En esta sección
Palabras clave de C#
Operadores de C#
Caracteres especiales de C#
Directivas de preprocesador de C#
Ofrece vínculos para información sobre los comandos del compilador para incrustar en
código fuente de C#.
Hay una serie de nuevas características implementadas en C# 7.0. Entre ellas, está la
coincidencia de patrones, las funciones locales, las declaraciones de variable out, las
expresiones throw, los literales binarios y los separadores de dígitos. Esta carpeta
contiene las especificaciones para cada una de esas características.
C# 7.3 es otra versión secundaria que incluye una serie de pequeñas actualizaciones.
Puede usar nuevas restricciones en parámetros de tipo genérico. Otros cambios hacen
que sea más fácil trabajar con campos fixed , incluido el uso de asignaciones stackalloc.
Las variables locales declaradas con la palabra clave ref pueden reasignarse para que
hagan referencia a un almacenamiento nuevo. Puede colocar atributos en propiedades
implementadas automáticamente que tengan como destino el campo de respaldo
generado por el compilador. Se pueden usar variables de expresión en inicializadores.
Las tuplas pueden compararse para comprobar si son iguales (o si no lo son). Además,
se han realizado algunas mejoras en la resolución de sobrecarga.
C# 8.0 está disponible con .NET Core 3.0. Entre las características se incluyen tipos de
referencia que aceptan valores NULL, coincidencia de patrones recursiva, métodos de
interfaz predeterminados, secuencias asincrónicas, intervalos e índices, using basado en
patrones y declaraciones using, asignación de uso combinado de NULL y miembros de
instancia de solo lectura.
Propuestas de especificaciones de C# 9
C# 9 está disponible con .NET 5. Entre las características disponibles se incluyen las
siguientes: registros, instrucciones de nivel superior, mejoras en la coincidencia de
patrones, establecedores solo de inicialización, nuevas expresiones con tipo de destino,
inicializadores de módulos, ampliación de los métodos parciales, funciones anónimas
estáticas, expresiones condicionales con tipo de destino, tipos de retorno de
covariantes, extensión GetEnumerator en bucles Foreach, parámetros de descarte de
expresiones lambda, atributos en funciones locales, enteros de tamaño nativo, punteros
de funciones, supresión de la emisión de marcas localsinit y anotaciones de parámetros
de tipos sin restricciones.
C# 10 está disponible con .NET 6. Las características incluyen estructuras de registro,
constructores de estructuras sin parámetros, directivas using globales, espacios de
nombres de ámbito de archivo, patrones de propiedades ampliados, cadenas
interpoladas mejoradas, cadenas interpoladas constantes, mejoras de lambda, expresión
de autor de llamada-argumento, directivas #line mejoradas, atributos genéricos,
análisis de asignaciones definitivas mejorados e invalidación de AsyncMethodBuilder .
Secciones relacionadas
Uso del entorno de desarrollo de Visual Studio para C#
Ofrece vínculos para los temas de tareas y conceptos y temas que describen el IDE y el
Editor.
Guía de programación de C#
C# 11 solo se admite en .NET 7 y versiones más recientes. C# 10 solo se admite en
.NET 6 y versiones más recientes. C# 9 solo se admite en .NET 5 y versiones más
recientes.
Valores predeterminados
El compilador determina un valor predeterminado según estas reglas:
Cuando el proyecto tiene como destino un marco en versión preliminar que tenga una
versión de lenguaje preliminar correspondiente, la versión de lenguaje que se usa es la
que está en versión preliminar. Puede usar las características más recientes con esa
versión preliminar en cualquier entorno, sin que afecte a los proyectos que tienen como
destino una versión de .NET Core publicada.
) Importante
Sugerencia
Para saber qué versión de lenguaje está usando actualmente, incluya #error
version (con distinción de mayúsculas y minúsculas) en el código. Esto hace que el
XML
<PropertyGroup>
<LangVersion>preview</LangVersion>
</PropertyGroup>
El valor preview usa la versión preliminar más reciente disponible del lenguaje C# que
admite el compilador.
XML
<Project>
<PropertyGroup>
<LangVersion>preview</LangVersion>
</PropertyGroup>
</Project>
Las compilaciones de todos los subdirectorios del directorio que contenga ese archivo
usarán la sintaxis de la versión preliminar de C#. Para obtener más información, consulte
Personalización de la compilación.
Valor Significado
latest El compilador acepta la sintaxis de la última versión del compilador (incluida las
versión secundaria).
latestMajor
El compilador acepta la sintaxis de la versión principal más reciente del
o bien compilador.
default
9.0 El compilador solo acepta la sintaxis que se incluye en C# 9 o versiones anteriores.
ISO-2
El compilador acepta solo la sintaxis que se incluye en ISO/IEC 23270:2006 C# (2.0).
o bien 2
Valor Significado
ISO-1
El compilador acepta solo la sintaxis que se incluye en ISO/IEC 23270:2003 C#
o bien 1 (1.0/1.2).
Tipos de valor (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
Los tipos de valor y los tipos de referencia son las dos categorías principales de tipos de
C#. Una variable de un tipo de valor contiene una instancia del tipo. Esto difiere de una
variable de un tipo de referencia, que contiene una referencia a una instancia del tipo.
De forma predeterminada, al asignar, pasar un argumento a un método o devolver el
resultado de un método, se copian los valores de variable. En el caso de las variables de
tipo de valor, se copian las instancias de tipo correspondientes. En el ejemplo siguiente
se muestra ese comportamiento:
C#
using System;
public int X;
public int Y;
var p2 = p1;
p2.Y = 200;
Console.WriteLine($"{nameof(p2)}: {p2}");
MutateAndDisplay(p2);
p.X = 100;
// Expected output:
C#
using System;
using System.Collections.Generic;
public TaggedInteger(int n)
Number = n;
n1.AddTag("A");
var n2 = n1;
n2.Number = 7;
n2.AddTag("B");
7 Nota
Para que el código sea menos propenso a errores y más sólido, defina y use tipos
de valor inmutables. En este artículo se usan tipos de valor mutables solo con fines
de demostración.
Un tipo de valor que admite un valor NULL T? representa todos los valores de su tipo de
valor T subyacente y un valor null adicional. No se puede asignar null a una variable
de un tipo de valor, a menos que sea un tipo de valor que acepte valores NULL.
Puede usar la restricción struct para especificar que un parámetro de tipo es un tipo de
valor que no acepta valores NULL. Los tipos de estructura y enumeración satisfacen la
restricción struct . Puede usar System.Enum en una restricción de clase base (conocida
como la restricción de enumeración) para especificar que un parámetro de tipo es un
tipo de enumeración.
Todos los tipos simples son tipos de estructuras y se diferencian de otros tipos de
estructuras en que permiten determinadas operaciones adicionales:
Se pueden usar literales para proporcionar un valor de un tipo simple. Por ejemplo,
'A' es un literal del tipo char y 2001 es un literal del tipo int .
Puede declarar constantes de los tipos simples con la palabra clave const. No es
posible tener constantes de otros tipos de estructuras.
Las expresiones constantes, cuyos operandos son todas constantes de los tipos
simples, se evalúan en tiempo de compilación.
Tipos de valor
Tipos simples
Variables
Vea también
Referencia de C#
System.ValueType
Tipos de referencia
Tipos numéricos enteros (referencia de
C#)
Artículo • 15/02/2023 • Tiempo de lectura: 6 minutos
Los tipos numéricos integrales representan números enteros. Todos los tipos numéricos
integrales son tipos de valor. También son tipos simples y se pueden inicializar con
literales. Todos los tipos numéricos enteros admiten operadores aritméticos, lógicos bit
a bit, de comparación y de igualdad.
C#
int a = 123;
System.Int32 b = 123;
Los tipos nint y nuint de las dos últimas filas de la tabla son enteros de tamaño nativo.
A partir de C# 9.0, puede usar las palabras clave nint y nuint para definir enteros de
tamaño nativo. Son enteros de 32 bits cuando se ejecutan en un proceso de 32 bits, o
bien enteros de 64 bits cuando se ejecutan en un proceso de 64 bits. Se pueden usar
para escenarios de interoperabilidad, bibliotecas de bajo nivel y para optimizar el
rendimiento en escenarios en los que se utilice la aritmética de enteros.
Los tipos enteros de tamaño nativo se representan internamente como los tipos
System.IntPtr y System.UIntPtr de .NET. A partir de C# 11, los tipos nint y nuint son
alias de los tipos subyacentes.
Cada uno de los tipos enteros tiene las constantes MinValue y MaxValue que
proporcionan el valor mínimo y máximo de ese tipo. Estas propiedades son constantes
en tiempo de compilación, excepto en el caso de los tipos de tamaño nativo ( nint y
nuint ). Las propiedades MinValue y MaxValue se calculan en tiempo de ejecución para
los tipos de tamaño nativo. Los tamaños de esos tipos dependen de la configuración del
proceso.
Literales enteros
Los literales enteros pueden ser
7 Nota
7 Nota
Puede usar la letra minúscula l como sufijo. Sin embargo, esto genera una
advertencia del compilador porque la letra l se confunde fácilmente con el
dígito 1 . Use L para mayor claridad.
C#
byte a = 17;
Como se muestra en el ejemplo anterior, si el valor del literal no está dentro del
intervalo del tipo de destino, se produce el error CS0031 del compilador.
También puede usar una conversión para convertir el valor representado por un literal
entero en un tipo que no sea el determinado del literal:
C#
Conversiones
Puede convertir un tipo numérico entero en cualquier otro tipo numérico entero. Si el
tipo de destino puede almacenar todos los valores del tipo de origen, la conversión es
implícita. De lo contrario, debe usar una expresión Cast para realizar una conversión
explícita. Para obtener más información, consulte Conversiones numéricas integradas.
C#
//size of nint = 8
//size of nuint = 8
//size of nint = 4
//size of nuint = 4
Para obtener los valores mínimo y máximo de los enteros de tamaño nativo en
tiempo de ejecución, use MinValue y MaxValue como propiedades estáticas con las
palabras clave nint y nuint , como en el ejemplo siguiente:
C#
Console.WriteLine($"nint.MinValue = {nint.MinValue}");
Console.WriteLine($"nint.MaxValue = {nint.MaxValue}");
Console.WriteLine($"nuint.MinValue = {nuint.MinValue}");
Console.WriteLine($"nuint.MaxValue = {nuint.MaxValue}");
//nint.MinValue = -9223372036854775808
//nint.MaxValue = 9223372036854775807
//nuint.MinValue = 0
//nuint.MaxValue = 18446744073709551615
//nint.MinValue = -2147483648
//nint.MaxValue = 2147483647
//nuint.MinValue = 0
//nuint.MaxValue = 4294967295
No hay ninguna sintaxis directa para los literales de entero de tamaño nativo. No
hay ningún sufijo para indicar que un literal es un entero de tamaño nativo, como
L para indicar long . En su lugar, puede usar conversiones implícitas o explícitas de
nint a = 42
nint a = (nint)42;
Tipos enteros
Literales enteros
C# 9: tipos enteros de tamaño nativo
C# 11: IntPtr numérico y UIntPtr
Vea también
Referencia de C#
Tipos de valor
Tipos de punto flotante
Cadenas con formato numérico estándar
Valores numéricos en .NET
Tipos numéricos de punto flotante
(referencia de C#)
Artículo • 05/10/2022 • Tiempo de lectura: 4 minutos
Los tipos numéricos de punto flotante representan números reales. Todos los tipos
numéricos de punto flotante son tipos de valor. También son tipos simples y se pueden
inicializar con literales. Todos los tipos de punto flotante numéricos admiten operadores
aritméticos, de comparación y de igualdad.
C#
double a = 12.3;
System.Double b = 12.3;
El valor predeterminado de cada tipo de punto flotante es cero, 0 . Cada uno de los
tipos de punto flotante tiene las constantes MinValue y MaxValue que proporcionan el
valor finito mínimo y máximo de ese tipo. Los tipos float y double también brindan
constantes que representan valores infinitos y no numéricos. Por ejemplo, el tipo double
ofrece las siguientes constantes: Double.NaN, Double.NegativeInfinity y
Double.PositiveInfinity.
El tipo de decimal es adecuado cuando el grado de precisión requerido viene
determinado por el número de dígitos a la derecha del separador decimal. Estos
números se utilizan normalmente en aplicaciones financieras, para importes monetarios
(por ejemplo, 1,00 $), tasas de interés (por ejemplo, 2,625 %), etc. Los números pares
que son precisos únicamente para un dígito decimal se controlan de forma más precisa
en el tipo decimal : 0,1, por ejemplo, se puede representar exactamente mediante una
instancia de decimal , mientras que no hay una instancia double o float que representa
exactamente 0,1. Debido a esta diferencia en los tipos numéricos, se pueden producir
errores de redondeo inesperados en cálculos aritméticos cuando se usa double o float
para datos decimales. Puede usar double en lugar de decimal cuando optimizar el
rendimiento es más importante que asegurar la precisión. Sin embargo, cualquier
diferencia de rendimiento pasaría desapercibida para todas las aplicaciones, salvo las
que consumen más cálculos. Otra posible razón para evitar decimal es minimizar los
requisitos de almacenamiento. Por ejemplo, ML.NET usa float porque la diferencia
entre 4 bytes y 16 bytes se acumula para conjuntos de datos muy grandes. Para obtener
más información, vea System.Decimal.
En una expresión, puede combinar tipos enteros y tipos float y double . En este caso,
los tipos enteros se convierten implícitamente en uno de los tipos de punto flotante y, si
es necesario, el tipo float se convierte implícitamente en double . La expresión se
evalúa de la siguiente forma:
Si hay un tipo double en la expresión, esta se evalúa como double , o bien como
bool en comparaciones relacionales o de igualdad.
Si no hay un tipo double en la expresión, esta se evalúa como float , o bien como
bool en comparaciones relacionales o de igualdad.
También es posible combinar en una expresión tipos enteros y el tipo decimal . En este
caso, los tipos enteros se convierten implícitamente en el tipo decimal y la expresión se
evalúa como decimal , o bien como bool en comparaciones relacionales y de igualdad.
En una expresión, no se puede mezclar el tipo decimal con los tipos float y double . En
este caso, si quiere realizar operaciones aritméticas, de comparación o de igualdad,
debe convertir explícitamente los operandos del tipo decimal o a este mismo tipo,
como se muestra en el ejemplo siguiente:
C#
double a = 1.0;
decimal b = 2.1m;
Console.WriteLine(a + (double)b);
Console.WriteLine((decimal)a + b);
Puede usar cadenas con formato numérico estándar o cadenas con formato numérico
personalizado para dar formato a un valor de punto flotante.
Literales reales
El tipo de un literal real viene determinado por su sufijo, como se indica a continuación:
C#
double d = 3D;
d = 4d;
d = 3.934_001;
float f = 3_000.5F;
f = 5.4f;
myMoney = 400.75M;
También puede usar la notación científica; es decir, especificar una parte exponencial de
un literal real, como se muestra en el ejemplo siguiente:
C#
double d = 0.42e2;
Console.WriteLine(d); // output 42
float f = 134.45E-2f;
decimal m = 1.5E6m;
Conversiones
Solo hay una conversión implícita entre los tipos numéricos de punto flotante: de float
a double . Sin embargo, puede convertir un tipo de punto flotante a cualquier otro tipo
de punto flotante con la conversión explícita. Para obtener más información, consulte
Conversiones numéricas integradas.
Vea también
Referencia de C#
Tipos de valor
Tipos enteros
Cadenas con formato numérico estándar
Valores numéricos en .NET
System.Numerics.Complex
Conversiones numéricas integradas
(referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 4 minutos
De En
byte short , ushort , int , uint , long , ulong , float , double , decimal , nint o nuint
ushort int , uint , long , ulong , float , double , o decimal , nint , o nuint
float double
7 Nota
Las conversiones implícitas de int , uint , long , ulong , nint o nuint a float y de
long , ulong , nint o nuint a double pueden provocar una pérdida de precisión,
No hay ninguna conversión implícita a los tipos byte y sbyte . No hay ninguna
conversión implícita de los tipos double y decimal .
C#
byte a = 13;
De En
byte sbyte
long sbyte , byte , short , ushort , int , uint , ulong , nint o nuint
De En
ulong sbyte , byte , short , ushort , int , uint , long , nint o nuint
float sbyte , byte , short , ushort , int , uint , long , ulong , decimal , nint o nuint
double sbyte , byte , short , ushort , int , uint , long , ulong , float , decimal , nint o nuint
decimal sbyte , byte , short , ushort , int , uint , long , ulong , float , double , nint o nuint
7 Nota
Al convertir un valor de tipo entero en otro tipo entero, el resultado depende del
contexto de comprobación de desbordamiento. En un contexto comprobado, la
conversión se realiza correctamente si el valor de origen está dentro del intervalo
del tipo de destino. De lo contrario, se produce una excepción OverflowException.
En un contexto no comprobado, la conversión siempre se realiza correctamente y
continúa así:
Cuando convierte un valor decimal en un tipo entero, este valor se redondea hacia
cero al valor entero más cercano. Si el valor entero resultante está fuera del rango
del tipo de destino, se genera una excepción OverflowException.
Al convertir un valor double o float en un tipo entero, este valor se redondea
hacia cero al valor entero más cercano. Si el valor entero resultante está fuera del
intervalo del tipo de destino, el resultado depende del contexto de comprobación
de desbordamiento. En un contexto comprobado, se genera una excepción
OverflowException, mientras que en un contexto no comprobado, el resultado es
un valor no especificado del tipo de destino.
Vea también
Referencia de C#
Conversiones de tipos
bool (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Para realizar operaciones lógicas con valores del tipo bool , use operadores lógicos
booleanos. El tipo bool es el tipo de resultado de los operadores de comparación e
igualdad. Una expresión bool puede ser una expresión condicional de control en las
instrucciones if, do, while y for, así como en el operador condicional ?:.
Literales
Puede usar los literales true y false para inicializar una variable bool o para pasar un
valor bool :
C#
Para más información sobre los tipos de valor que admiten un valor NULL, consulte
Tipos de valor que admiten un valor NULL.
Conversiones
C# solo proporciona dos conversiones que implican al tipo bool . Son una conversión
implícita al tipo bool? que acepta valores NULL correspondiente y una conversión
explícita del tipo bool? . Sin embargo, .NET proporciona métodos adicionales que se
pueden usar para realizar una conversión al tipo bool , o bien revertirla. Para obtener
más información, vea la sección Convertir a y desde valores booleanos de la página de
referencia de la API System.Boolean.
Vea también
Referencia de C#
Tipos de valor
operadores true y false
char (referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
La palabra clave de tipo char es un alias para el tipo de estructura de .NET System.Char
que representa un carácter Unicode UTF-16.
Literales
Puede especificar un valor de char con:
un literal de carácter.
una secuencia de escape Unicode, que es \u seguido de la representación
hexadecimal de cuatro símbolos de un código de carácter.
una secuencia de escape hexadecimal, que es \x seguido de la representación
hexadecimal de un código de carácter.
C#
'j',
'\u006A',
'\x006A',
(char)106,
};
En el caso de una secuencia de escape Unicode, debe especificar los cuatro dígitos
hexadecimales. Es decir, \u006A es una secuencia de escape válida, mientras que
\u06A y \u6A no son válidas.
Conversiones
El tipo char se puede convertir implícitamente en los tipos enteros siguientes: ushort ,
int , uint , long y ulong . También se puede convertir implícitamente en los tipos
numéricos de punto flotante integrados: float , double y decimal . Se puede convertir
explícitamente en los tipos enteros sbyte , byte y short .
No hay ninguna conversión implícita de otros tipos al tipo char . Sin embargo, cualquier
tipo numérico entero o de punto flotante es implícitamente convertible a char .
Vea también
Referencia de C#
Tipos de valor
Cadenas
System.Text.Rune
Codificación de caracteres de .NET
Tipos de enumeración (referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 4 minutos
C#
enum Season
Spring,
Summer,
Autumn,
Winter
C#
None = 0,
Unknown = 1,
ConnectionLost = 100,
OutlierReading = 200
C#
[Flags]
None = 0b_0000_0000, // 0
Monday = 0b_0000_0001, // 1
Tuesday = 0b_0000_0010, // 2
Wednesday = 0b_0000_0100, // 4
Thursday = 0b_0000_1000, // 8
Friday = 0b_0001_0000, // 16
Saturday = 0b_0010_0000, // 32
Sunday = 0b_0100_0000, // 64
Console.WriteLine(meetingDays);
// Output:
// Output:
// Output:
var a = (Days)37;
Console.WriteLine(a);
// Output:
Para más información y ver algunos ejemplos, consulte la página de referencia de API de
System.FlagsAttribute y la sección miembros no exclusivos y el atributo Flags de la
página de referencia de API de System.Enum.
Puede usar System.Enum en una restricción de clase base (conocida como la restricción
de enumeración) para especificar que un parámetro de tipo es un tipo de enumeración.
Cualquier tipo de enumeración también cumple la restricción struct , que se usa para
especificar que un parámetro de tipo es un tipo de valor que no acepta valores NULL.
Conversiones
Para cualquier tipo de enumeración, existen conversiones explícitas entre el tipo de
enumeración y su tipo entero subyacente. Si convierte (usacast) un valor de
enumeración a su tipo subyacente, el resultado es el valor entero asociado de un
miembro de enumeración.
C#
Spring,
Summer,
Autumn,
Winter
Season a = Season.Autumn;
Console.WriteLine($"Integral value of {a} is {(int)a}"); // output:
Integral value of Autumn is 2
var b = (Season)1;
var c = (Season)4;
Console.WriteLine(c); // output: 4
Enumeraciones
Operaciones y valores de enumeración
Operadores lógicos de enumeración
Operadores de comparación de enumeración
Conversiones de enumeración explícitas
Conversiones de enumeración implícitas
Vea también
Referencia de C#
Cadenas de formato de enumeración
Instrucciones de diseño: diseño de enumeraciones
Instrucciones de diseño: convenciones de nomenclatura de enumeración
Expresión
Instrucción switch
Tipos de estructura (Referencia de C#)
Artículo • 09/03/2023 • Tiempo de lectura: 10 minutos
Un tipo de estructura (o tipo struct) es un tipo de valor que puede encapsular datos y
funcionalidad relacionada. Para definir un tipo de estructura se usa la palabra clave
struct :
C#
X = x;
Y = y;
Para obtener información sobre los tipos ref struct y readonly ref struct , consulte el
artículo tipos de estructura de referencia.
Los tipos de estructura tienen semántica de valores. Es decir, una variable de un tipo de
estructura contiene una instancia del tipo. De forma predeterminada, los valores de
variable se copian al asignar, pasar un argumento a un método o devolver el resultado
de un método. Para las variables de tipo estructura, se copia una instancia del tipo. Para
más información, vea Tipos de valor.
Normalmente, los tipos de estructura se usan para diseñar tipos de pequeño tamaño
centrados en datos que proporcionan poco o ningún comportamiento. Por ejemplo, en
.NET se usan los tipos de estructura para representar un número (entero y real), un valor
booleano, un caracter Unicode, una instancia de tiempo. Si le interesa el
comportamiento de un tipo, considere la posibilidad de definir una clase. Los tipos de
clase tienen semántica de referencias. Es decir, una variable de un tipo de clase contiene
una referencia a una instancia del tipo, no la propia instancia.
Dado que los tipos de estructura tienen semántica del valor, le recomendamos que
defina tipos de estructura inmutables.
Estructura readonly
Se usa el modificador readonly para declarar que un tipo de estructura es inmutable.
Todos los miembros de datos de una estructura readonly debe ser de solo lectura tal
como se indica a continuación:
Esto garantiza que ningún miembro de una estructura readonly modifique el estado de
la misma. Eso significa que otros miembros de instancia, excepto los constructores, son
implícitamente readonly.
7 Nota
C#
X = x;
Y = y;
Métodos:
C#
return X + Y;
También puede aplicar el modificador readonly a los métodos que invalidan los
métodos declarados en System.Object:
C#
Propiedades e indizadores:
C#
C#
Mutación no destructiva
A partir de C# 10, puede usar la expresión with para generar una copia de una instancia
de tipo de estructura con las propiedades y los campos especificados modificados.
Como se muestra en el ejemplo siguiente, se usa la sintaxis del inicializador de objeto
para especificar qué miembros se van a modificar y sus nuevos valores.
C#
X = x;
Y = y;
var p2 = p1 with { X = 3 };
var p3 = p1 with { X = 1, Y = 4 };
Estructura record
A partir de C# 10, puede definir tipos de estructura de registro. Los tipos de registro
proporcionan funcionalidad integrada para encapsular datos. Puede definir tipos record
struct y readonly record struct . Una estructura de registro no puede ser una ref
C#
public Measurement()
Value = double.NaN;
Description = "Undefined";
Value = value;
Description = description;
var m2 = default(Measurement);
Console.WriteLine(m2); // output: 0 ()
C#
Todos los campos miembros de una estructura deben asignarse definitivamente cuando
se crean porque los tipos struct almacenan directamente sus datos. El valor default de
una estructura ha asignado definitivamente todos los campos a 0. Todos los campos
deben asignarse definitivamente cuando se invoca un constructor. Los campos se
inicializan mediante los mecanismos siguientes:
C#
Value = value;
Value = value;
Description = description;
Description = description;
Console.WriteLine(m2); // output: 0 ()
var m3 = default(Measurement);
Console.WriteLine(m3); // output: 0 ()
Cada struct tiene un constructor sin parámetros public . Si escribe un constructor sin
parámetros, debe ser público. Si una estructura declara cualquier inicializador de campo,
debe declarar explícitamente un constructor. No hace falta que dicho constructor no
tenga parámetros. Si una estructura declara un inicializador de campo pero no declara
ningún constructor, el compilador notifica un error. Cualquier constructor declarado
explícitamente (con parámetros o sin parámetros) ejecuta todos los inicializadores de
campo de esa estructura. Todos los campos sin un inicializador de campo o una
asignación en un constructor se establecen en el valor predeterminado. Para obtener
más información, vea la nota propuesta de la característica Constructores de structs sin
parámetros.
C#
public double x;
public double y;
Coords p;
p.x = 3;
p.y = 4;
En el caso de los tipos de valor integrados, use los literales correspondientes para
especificar un valor del tipo.
Restricción struct
Use también la palabra clave struct de la restricción struct para especificar que un
parámetro de tipo es un tipo de valor que no acepta valores NULL. Los tipos de
estructura y enumeración satisfacen la restricción struct .
Conversiones
Para cualquier tipo de estructura (excepto los tipos de ref struct), hay conversiones
boxing y unboxing a los tipos System.ValueType y System.Object y también desde ellos.
También existen conversiones boxing y unboxing entre un tipo de estructura y cualquier
interfaz que implemente.
Para más información sobre las características de struct , consulte las siguientes notas
de propuestas de características:
C#
// Output:
// Output:
Como se muestra en el ejemplo anterior, para definir un tipo de tupla, se especifican los
tipos de todos sus miembros de datos y, opcionalmente, los nombres de campos. No se
pueden definir métodos en un tipo de tupla, pero se pueden usar los métodos
proporcionados por .NET, como se muestra en el siguiente ejemplo:
C#
Console.WriteLine(t.ToString());
// Output:
// (4.5, 3)
Los tipos de tupla son tipos de valores; los elementos de tupla son campos públicos.
Esto hace que las tuplas sean tipos de valor mutables.
7 Nota
C#
var t =
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
Console.WriteLine(t.Item26); // output: 26
C#
var xs = new[] { 4, 7, 9 };
// Output:
// Output:
if (i < min)
min = i;
if (i > max)
max = i;
También puede utilizar tipos de tupla en lugar de tipos anónimos; por ejemplo, en las
consultas LINQ. Para obtener más información, vea Elección entre tipos de tupla y
anónimos.
C#
C#
var count = 3;
Los nombres predeterminados de los campos de tupla son Item1 , Item2 , Item3 , etc.
Siempre puede usar el nombre predeterminado de un campo, incluso cuando se
especifica un nombre de campo de forma explícita o inferida, como se muestra en el
siguiente ejemplo:
C#
var a = 1;
// Output:
Sugerencia
Habilite la regla de estilo de código .NET IDE0037 para establecer una preferencia
sobre los nombres de campo de tupla explícitos o inferidos.
Asignación y deconstrucción de tuplas
C# admite la asignación entre tipos de tupla que satisfacen estas dos condiciones:
C#
// Output:
t3 = t2;
// Output:
También puede usar el operador de asignación = para deconstruir una instancia de tupla
en variables independientes. Para ello, siga uno de estos métodos:
C#
// Output:
Usar la palabra clave var fuera de los paréntesis para declarar las variables con
tipo implícito y permitir que el compilador deduzca sus tipos:
C#
var t = ("post office", 3.6);
// Output:
C#
(destination, distance) = t;
// Output:
Para obtener más información sobre la deconstrucción de tuplas y otros tipos, consulte
Deconstrucción de tuplas y otros tipos.
Igualdad de tupla
Los tipos de tupla admiten los == operadores y != . Estos operadores comparan los
miembros del operando izquierdo con los miembros correspondientes del operando
derecho, siguiendo el orden de los elementos de la tupla.
C#
C#
int Display(int s)
Console.WriteLine(s);
return s;
// Output:
// 1
// 2
// 3
// 4
// False
C#
};
// Output:
Vea también
Referencia de C#
Tipos de valor
Elección entre tipos de tupla y anónimos
System.ValueTuple
Tipos de valor que admiten valores
NULL (referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 8 minutos
Un tipo de valor que acepta valores NULL T? representa todos los valores de su tipo de
valor T subyacente y un valor null adicional. Por ejemplo, puede asignar cualquiera de
los tres valores siguientes a una variable bool? : true , false o null . Un tipo de valor
subyacente T no puede ser un tipo de valor que acepte valores NULL por sí mismo.
Todos los tipos que admiten valores NULL son instancias de la estructura
System.Nullable<T> genérica. Puede hacer referencia a un tipo de valor que admite
valores NULL con un tipo subyacente T en cualquiera de las formas intercambiables
siguientes: Nullable<T> o T? .
Normalmente, los tipos de valor que admiten valores NULL se usan cuando es necesario
representar el valor indefinido de un tipo de valor subyacente. Por ejemplo, una variable
booleana, o bool , solo puede ser true o false . Sin embargo, en algunas aplicaciones,
un valor de variable puede estar sin definir o faltar. Por ejemplo, un campo de base de
datos puede contener true o false , o puede no contener ningún valor, es decir, NULL .
Puede usar el tipo bool? en ese escenario.
Declaración y asignación
Como un tipo de valor se puede convertir de forma implícita al tipo de valor que admite
valores NULL correspondiente, un valor se puede asignar a un tipo que admite valores
NULL como se haría para su tipo de valor subyacente. También se puede asignar el valor
null . Por ejemplo:
C#
double? pi = 3.14;
int m2 = 10;
int? m = m2;
C#
int? a = 42;
if (a is int valueOfA)
Console.WriteLine($"a is {valueOfA}");
else
// Output:
// a is 42
Siempre puede usar las siguientes propiedades de solo lectura para examinar y obtener
un valor de una variable de tipo de valor que admite valores NULL:
C#
int? b = 10;
if (b.HasValue)
Console.WriteLine($"b is {b.Value}");
else
// Output:
// b is 10
También se puede comparar una variable de un tipo de valor que admite valores NULL
con null en lugar de usar la propiedad HasValue , como se muestra en el ejemplo
siguiente:
C#
int? c = 7;
if (c != null)
Console.WriteLine($"c is {c.Value}");
else
// Output:
// c is 7
C#
int? a = 28;
int b = a ?? -1;
int? c = null;
int d = c ?? -1;
Si desea utilizar el valor predeterminado del tipo de valor subyacente en lugar de null ,
use el método Nullable<T>.GetValueOrDefault().
También puede convertir de forma explícita un tipo de valor que admite valores NULL
en uno que no los admite, como se indica en el ejemplo siguiente:
C#
int? n = null;
Un tipo de valor que no admite valores NULL T se convierte implícitamente al tipo que
admite valores NULL T? correspondiente.
Operadores de elevación
Los operadores unarios y binarios predefinidos o los operadores sobrecargados que
admite un tipo de valor T también se admiten en el tipo de valor que admite un valor
NULL T? correspondiente. Estos operadores, también conocidos como operadores de
elevación, generan un valor null si uno o los dos operandos son null ; de lo contrario,
el operador usa los valores contenidos de sus operandos para calcular el resultado. Por
ejemplo:
C#
int? a = 10;
int? b = null;
int? c = 10;
a++; // a is 11
a = a * c; // a is 110
a = a + b; // a is null
7 Nota
Para el tipo bool? , los operadores predefinidos & y | no siguen las reglas descritas
en esta sección: el resultado de una evaluación de operador puede ser distinto de
NULL incluso si uno de los operandos es null . Para más información, consulte la
sección Operadores lógicos booleanos que aceptan valores NULL del artículo
Operadores lógicos booleanos.
En el caso de los operadores de comparación < , > , <= y >= , si uno o ambos operandos
son null , el resultado es false ; de lo contrario, se comparan los valores contenidos de
los operandos. No asuma que, como una comparación determinada (por ejemplo, <= )
devuelve false , la comparación contraria ( > ) devolverá true . En el ejemplo siguiente se
muestra que 10 no es
C#
int? a = 10;
// Output:
// 10 == null is False
int? b = null;
int? c = null;
// Output:
Si existe una conversión definida por el usuario entre dos tipos de valor, esa misma
conversión también se puede usar entre los correspondientes tipos que aceptan valores
NULL.
C#
int a = 41;
object aBoxed = a;
// Output:
// Value of aNullable: 41
C#
// Output:
Si quiere determinar si una instancia es de un tipo de valor que admite un valor NULL,
no use el método Object.GetType para obtener una instancia de Type para probarla con
el código anterior. Cuando se llama al método Object.GetType en una instancia de un
tipo de valor que admite un valor NULL, se aplica la conversión boxing a la instancia
para convertirla en Object. Como la conversión boxing de una instancia que no es NULL
de un tipo de valor que admite valores NULL equivale a la conversión boxing de un
valor del tipo subyacente, GetType devuelve una instancia Type que representa el tipo
de valor subyacente que admite valores NULL:
C#
int? a = 17;
Console.WriteLine(typeOfA.FullName);
// Output:
// System.Int32
No use el operador is para determinar si una instancia es de un tipo de valor que admite
valores NULL. Como se muestra en el ejemplo siguiente, no se pueden distinguir los
tipos de instancias de un tipo de valor que admite valores NULL y su tipo subyacente
mediante el operador is :
C#
int? a = 14;
if (a is int)
int b = 17;
if (b is int?)
// Output:
7 Nota
Los métodos que se describen en esta sección no son aplicables en el caso de los
tipos de referencia nula.
Nullable types (Tipos que aceptan valores NULL [Guía de programación de C#])
Operadores de elevación
Conversiones implícitas que aceptan valores NULL
Conversiones explícitas que aceptan valores NULL
Operadores de conversión de elevación
Vea también
Referencia de C#
¿Qué significa exactamente "elevado"?
System.Nullable<T>
System.Nullable
Nullable.GetUnderlyingType
Tipos de referencia que aceptan valores null
Tipos de referencia (referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Hay dos clases de tipos en C#: tipos de referencia y tipos de valor. Las variables de tipos
de referencia almacenan referencias en sus datos (objetos), mientras que las variables de
tipos de valor contienen directamente los datos. Con los tipos de referencia, dos
variables pueden hacer referencia al mismo objeto y, por lo tanto, las operaciones en
una variable pueden afectar al objeto al que hace referencia la otra variable. Con los
tipos de valor, cada variable tiene su propia copia de los datos y no es posible que las
operaciones en una variable afecten a la otra (excepto en el caso de las variables de
parámetro in , ref y out ; consulte el modificador del parámetro in, ref y out).
class
interface
delegate
record
dynamic
object
string
Vea también
Referencia de C#
Palabras clave de C#
Tipos de puntero
Tipos de valor
Tipos de referencia integrados
(referencia de C#)
Artículo • 25/02/2023 • Tiempo de lectura: 11 minutos
C# tiene muchos tipos de referencia integrados. Tienen palabras clave u operadores que
son sinónimos para un tipo en la biblioteca de .NET.
El tipo de objeto
El tipo object es un alias de System.Object en .NET. En el sistema de tipos unificado de
C#, todos los tipos, los predefinidos y los definidos por el usuario, los tipos de referencia
y los tipos de valores, heredan directa o indirectamente de System.Object. Puede
asignar valores de cualquier tipo a las variables de tipo object . Cualquier variable
object puede asignarse a su valor predeterminado con el literal null . Cuando una
variable de un tipo de valor se convierte en objeto, se dice que se aplica la conversión
boxing. Cuando una variable de tipo object se convierte en un tipo de valor, se dice que
se aplica la conversión unboxing. Para obtener más información, vea Conversión boxing
y unboxing.
Tipo string
El tipo string representa una secuencia de cero o más caracteres Unicode. string es
un alias de System.String en .NET.
C#
string a = "hello";
string b = "h";
b += "ello";
Console.WriteLine(a == b);
Console.WriteLine(object.ReferenceEquals(a, b));
C#
C#
string b = "h";
b += "ello";
C#
De igual manera, el operador [] también puede usarse para recorrer en iteración cada
carácter en una cadena:
C#
// Output: t e s t
Literales de cadena
Los literales de cadena son de tipo string y se pueden escribir de tres formas: sin
formato, entre comillas y textuales.
Los literales de cadena sin formato están disponibles a partir de C# 11. Los literales de
cadena sin formato pueden contener texto arbitrario sin necesidad de secuencias de
escape. Los literales de cadena sin formato pueden incluir espacios en blanco, nuevas
líneas, comillas insertadas y otros caracteres especiales. Los literales de cadena sin
formato se incluyen entre tres comillas dobles como mínimo ("""):
C#
"""
This is a multi-line
"""
Incluso puede incluir una secuencia de tres caracteres (o más) de comillas dobles. Si el
texto requiere una secuencia de comillas insertada, se inicia y finaliza el literal de cadena
sin formato con más comillas, según sea necesario:
C#
"""""
This raw string literal has four """", count them: """" four!
You could extend this example with as many embedded quotes as needed for
your text.
"""""
Los literales de cadena sin formato suelen tener las secuencias de comillas iniciales y
finales en líneas independientes del texto insertado. Los literales de cadena sin formato
de varias líneas admiten cadenas entre comillas:
C#
""";
Console.WriteLine(message);
Cuando las comillas iniciales y finales están en líneas distintas, las líneas nuevas después
de la comilla inicial y final no se incluyen en el contenido final. La secuencia de comillas
de cierre dictamina la columna situada más a la izquierda del literal de cadena. Puede
aplicar sangría a un literal de cadena sin formato para que coincida con el formato de
código general:
C#
""";
Console.WriteLine(message);
C#
"prop": 0
""";
C#
Puede combinar literales de cadena sin formato con interpolación de cadenas para
incluir caracteres de comilla y llaves en la cadena de salida.
C#
Console.WriteLine(a);
// Output:
// \f
// F
7 Nota
Los literales de cadena textual empiezan por @ y también se incluyen entre comillas
dobles. Por ejemplo:
C#
C#
Para incluir una comilla doble en una cadena @entrecomillada, duplique esto:
C#
C#
Para almacenar un literal de cadena UTF-8 como una matriz, se requiere el uso de
ReadOnlySpan<T>.ToArray() para copiar los bytes que contienen el literal en la matriz
mutable:
C#
Tipo delegate
La declaración de un tipo delegado es similar a una firma de método. Tiene un valor
devuelto y un número cualquiera de parámetros de cualquier tipo:
C#
Un delegate es un tipo de referencia que puede usarse para encapsular un método con
nombre o anónimo. Los delegados son similares a los punteros de función en C++; pero
son más seguros y proporcionan mayor seguridad de tipos. Para las aplicaciones de
delegados, vea Delegados y Delegados genéricos. Los delegados son la base de los
eventos. Se pueden crear instancias de un delegado asociándolo a un método con
nombre o anónimo.
Para crear instancias del delegado debe usarse un método o una expresión lambda que
tenga un tipo de valor devuelto y parámetros de entrada compatibles. Para obtener más
información sobre el grado de variación permitida en la firma de método, vea Varianza
en delegados. Para el uso con métodos anónimos, el delegado y el código que se van a
asociar se declaran juntos.
C#
// at run time.
C#
A partir de C# 9, puede declarar punteros de función, que usan una sintaxis similar. Un
puntero de función usa la instrucción calli en lugar de crear instancias de un tipo
delegado y llamar al método virtual Invoke .
Tipo dynamic
El tipo dynamic indica el uso de la variable y las referencias a su comprobación de tipos
en el tiempo de compilación de omisión de miembros. En su lugar, se resuelven estas
operaciones en tiempo de ejecución. El tipo dynamic simplifica el acceso a las API de
COM como las API de automatización de Office, a API dinámicas como las bibliotecas de
IronPython, y a Document Object Model (DOM) HTML.
En el siguiente ejemplo se contrasta una variable de tipo dynamic con una variable de
tipo object . Para comprobar el tipo de cada variable en tiempo de compilación,
coloque el puntero del mouse sobre dyn u obj en las instrucciones WriteLine . Copie el
código siguiente en un editor donde IntelliSense esté disponible. IntelliSense muestra
dynamic para dyn y object para obj .
C#
class Program
dynamic dyn = 1;
object obj = 1;
// Rest the mouse pointer over dyn and obj to see their
System.Console.WriteLine(obj.GetType());
Las instrucciones WriteLine muestran los tipos en tiempo de ejecución de dyn y obj . En
ese punto, ambos tienen el mismo tipo, entero. Se produce el siguiente resultado:
Consola
System.Int32
System.Int32
Para ver la diferencia entre dyn y obj en tiempo de compilación, agregue las dos líneas
siguientes entre las declaraciones y las instrucciones WriteLine en el ejemplo anterior.
C#
dyn = dyn + 3;
obj = obj + 3;
C#
using System;
namespace DynamicExamples
class Program
Console.WriteLine(ec.ExampleMethod(10));
Console.WriteLine(ec.ExampleMethod("value"));
//Console.WriteLine(ec.ExampleMethod(10, 4));
Console.WriteLine(dynamic_ec.ExampleMethod(10));
//Console.WriteLine(dynamic_ec.ExampleMethod(10, 4));
class ExampleClass
int two = 2;
if (d is int)
return local;
else
return two;
// Results:
// Local variable
// 2
// Local variable
Vea también
Referencia de C#
Palabras clave de C#
Eventos
Uso de tipo dinámico
Procedimientos recomendados para el uso de cadenas
Operaciones básicas de cadenas
Creación de cadenas
Operadores de conversión y prueba de tipos
Procedimiento para convertir de forma segura mediante la coincidencia de
patrones y los operadores is y as
Tutorial: Crear y usar objetos dinámicos (C# y Visual Basic)
System.Object
System.String
System.Dynamic.DynamicObject
Registros (referencia de C#)
Artículo • 17/02/2023 • Tiempo de lectura: 19 minutos
A partir de C# 9, se usa la palabra clave record para definir un tipo de referencia que
proporciona funcionalidad integrada para encapsular los datos. C# 10 permite la sintaxis
record class como sinónimo para aclarar un tipo de referencia y record struct para
definir un tipo de valor con una funcionalidad similar. Puede crear tipos de registros con
propiedades inmutables mediante parámetros posicionales o sintaxis de propiedades
estándar.
En los dos ejemplos siguientes se muestran tipos de referencia record (o record class ):
C#
C#
};
C#
C#
C#
public record Person
};
Las estructuras de registro también pueden ser mutables, tanto estructuras de registro
posicionales como estructuras de registro sin parámetros posicionales:
C#
C#
Aunque los registros pueden ser mutables, están destinados principalmente a admitir
modelos de datos inmutables. El tipo de registro ofrece las siguientes características:
En los ejemplos anteriores se muestran algunas diferencias entre los registros que son
tipos de referencia y que son tipos de valor:
En el resto de este artículo se describen los tipos record class y record struct . Las
diferencias se detallan en cada sección. Debe elegir entre record class y record
struct , de la misma forma que se elige entre class y struct . El término registro se usa
para describir el comportamiento que se aplica a todos los tipos de registro. Se usa
record struct o record class para describir el comportamiento que se aplica solo a los
C#
Console.WriteLine(person);
C#
/// <summary>
/// </summary>
/// <remarks>
/// properties for the first and last name. Those properties
/// </remarks>
C#
Un tipo de registro no tiene que declarar ninguna propiedad posicional. Puede declarar
un registro sin propiedades posicionales, y otros campos y propiedades, como en el
ejemplo siguiente:
C#
};
Inmutabilidad
Un registro posicional y una estructura de registro de solo lectura posicional declaran
propiedades de solo inicialización. Una estructura de registro posicional declara
propiedades de lectura y escritura. Puede invalidar cualquiera de esos valores
predeterminados, como se ha mostrado en la sección anterior.
La inmutabilidad puede resultar útil si necesita que un tipo centrado en datos sea
seguro para subprocesos o si depende de que un código hash quede igual en una tabla
hash. Sin embargo, la inmutabilidad no es adecuada para todos los escenarios de datos.
Por ejemplo, Entity Framework Core no admite la actualización con tipos de entidad
inmutables.
C#
public record Person(string FirstName, string LastName, string[]
PhoneNumbers);
person.PhoneNumbers[0] = "555-6789";
Igualdad de valores
Si no invalida o reemplaza métodos de igualdad, el tipo que declara rige cómo se define
la igualdad:
Para los tipos class , dos objetos son iguales si hacen referencia al mismo objeto
en memoria.
Para los tipos struct , dos objetos son iguales si son del mismo tipo y almacenan
los mismos valores.
Para los tipos record , incluidos record struct y readonly record struct , dos
objetos son iguales si son del mismo tipo y almacenan los mismos valores.
person1.PhoneNumbers[0] = "555-1234";
Mutación no destructiva
Si necesita copiar una instancia de registro con algunas modificaciones, puede usar una
expresión with para lograr una mutación no destructiva. Una expresión with crea una
instancia de registro que es una copia de una instancia de registro existente, con las
propiedades y los campos especificados modificados. Use la sintaxis del inicializador de
objeto para especificar los valores que se van a cambiar, como se muestra en el ejemplo
siguiente:
C#
Console.WriteLine(person1);
Console.WriteLine(person2);
Console.WriteLine(person2);
A fin de implementar esta característica para los tipos record class , el compilador
sintetiza un método de clonación y un constructor de copia. El método de clonación
virtual devuelve un nuevo registro inicializado por el constructor de copia. Cuando se
usa una expresión with , el compilador crea código que llama al método de clonación y,
después, establece las propiedades que se especifican en la expresión with .
compilador.
La cadena impresa para <value> es la cadena devuelta por ToString() para el tipo de la
propiedad. En el ejemplo siguiente, ChildNames es System.Array, donde ToString
devuelve System.String[] :
Para implementar esta característica, en los tipos record class el compilador sintetiza
un método PrintMembers virtual y una invalidación de ToString. En los tipos record
struct , este miembro es private .
La invalidación ToString crea un objeto StringBuilder
C#
stringBuilder.Append(" { ");
if (PrintMembers(stringBuilder))
stringBuilder.Append(" ");
stringBuilder.Append("}");
return stringBuilder.ToString();
Herencia
Esta sección solo se aplica a los tipos record class .
Un registro puede heredar de otro registro. Sin embargo, un registro no puede heredar
de una clase, y una clase no puede heredar de un registro.
C#
: Person(FirstName, LastName);
Console.WriteLine(teacher);
C#
: Person(FirstName, LastName);
: Person(FirstName, LastName);
En el ejemplo, todas las variables se declaran como Person , incluso cuando la instancia
es un tipo derivado de Student o Teacher . Las instancias tienen las mismas propiedades
y los mismos valores de propiedad. Pero student == teacher devuelve False , aunque
ambas son variables de tipo Person , y student == student2 devuelve True , aunque una
es una variable Person y otra es una variable Student . La prueba de igualdad depende
del tipo en tiempo de ejecución del objeto real, no del tipo declarado de la variable.
Para implementar este comportamiento, el compilador sintetiza una propiedad
EqualityContract que devuelve un objeto Type que coincide con el tipo del registro.
EqualityContract permite a los métodos de igualdad comparar el tipo en tiempo de
C#
};
};
Console.WriteLine(p2);
Console.WriteLine(p3);
C#
: Person(FirstName, LastName);
: Person(FirstName, LastName);
Console.WriteLine(teacher);
Para un registro sealed que deriva de object (no declara un registro base):
private bool PrintMembers(StringBuilder builder) .
Para un registro sealed que se deriva de otro registro (tenga en cuenta que el tipo
envolvente es sealed , por lo que el método tiene un estado sealed eficaz):
protected override bool PrintMembers(StringBuilder builder) .
Para un registro que no es sealed y que deriva del objeto: protected virtual bool
PrintMembers(StringBuilder builder); .
C#
public abstract record Person(string FirstName, string LastName, string[]
PhoneNumbers)
stringBuilder.Append($"PhoneNumber1 = {PhoneNumbers[0]},
PhoneNumber2 = {PhoneNumbers[1]}");
return true;
if (base.PrintMembers(stringBuilder))
stringBuilder.Append(", ");
};
stringBuilder.Append($"Grade = {Grade}");
return true;
};
Console.WriteLine(teacher);
7 Nota
C#
: Person(FirstName, LastName);
: Person(FirstName, LastName);
Restricciones genéricas
No hay ninguna restricción genérica en la que sea necesario que un tipo sea un registro.
Los registros satisfacen la restricción class o struct . Para realizar una restricción en
una jerarquía específica de tipos de registro, coloque la restricción en el registro base
como lo haría con una clase base. Para obtener más información, vea Restricciones de
tipos de parámetros.
Registros
Establecedores de solo inicialización
Valores devueltos de covariante
Vea también
Referencia de C#
Instrucciones de diseño: elección entre clase y estructura
Instrucciones de diseño: diseño de estructuras
El sistema de tipos de C#
Expresión with
class (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Las clases se declaran mediante la palabra clave class , como se muestra en el siguiente
ejemplo:
C#
class TestClass
Comentarios
Solo la herencia simple se permite en C#. En otras palabras, una clase puede heredar la
implementación solo de una clase base. En cambio, una clase puede implementar más
de una interfaz. En la tabla siguiente se muestran ejemplos de herencia de clases e
implementación de interfaces:
Herencia Ejemplo
Las clases que se declaran directamente dentro de un espacio de nombres, que no están
anidadas dentro de otras clases, pueden ser de tipo public o internal. De forma
predeterminada, las clases son internal .
Los miembros de clase, incluidas las clases anidadas, pueden ser public, protected
internal, protected, internal, private o private protected. De forma predeterminada, los
miembros son private .
Puede declarar clases genéricas que tengan parámetros de tipo. Para obtener más
información, consulte Clases genéricas.
Una clase puede contener declaraciones de los miembros siguientes:
Constructores
Constantes
Fields
Finalizadores
Métodos
Propiedades
Indizadores
Operadores
Eventos
Delegados
Clases
Interfaces
Tipos de estructura
Tipos de enumeración
Ejemplo
En el ejemplo siguiente se muestra cómo declarar campos de clase, constructores y
métodos. También se muestra la creación de instancias de objeto y la impresión de
datos de instancias. En este ejemplo, se declaran dos clases. La primera clase, Child ,
contiene dos campos privados ( name y age ), dos constructores públicos y un método
público. La segunda clase, StringTest , se usa para contener Main .
C#
class Child
// Default constructor:
public Child()
name = "N/A";
// Constructor:
this.name = name;
this.age = age;
// Printing method:
class StringTest
// Display results:
child1.PrintChild();
child2.PrintChild();
child3.PrintChild();
/* Output:
*/
Comentarios
Observe que en el ejemplo anterior solo se puede acceder a los campos privados ( name
y age ) a través del método público de la clase Child . Por ejemplo, no puede imprimir el
nombre de la clase child, desde el método Main , mediante una instrucción como esta:
C#
Console.Write(child1.name); // Error
El acceso a miembros privados de Child desde Main solo sería posible si Main fuera un
miembro de la clase.
Los tipos declarados dentro de una clase sin un modificador de acceso adoptan el valor
predeterminado de private , por lo que los miembros de datos de este ejemplo
seguirían siendo private si se quitara la palabra clave.
Por último, tenga en cuenta que, para el objeto creado mediante el constructor sin
parámetros ( child3 ), el campo age se ha inicializado en cero de forma predeterminada.
Consulte también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Tipos de referencia
interface (Referencia de C#)
Artículo • 17/02/2023 • Tiempo de lectura: 5 minutos
Una interfaz define un contrato. Cualquier class o struct que implemente ese contrato
debe proporcionar una implementación de los miembros definidos en la interfaz. Una
interfaz puede definir una implementación predeterminada de miembros. También
puede definir miembros static para proporcionar una única implementación de
funcionalidad común. A partir de C# 11, una interfaz puede definir miembros static
abstract o static virtual para declarar que un tipo de implementación debe
proporcionar los miembros declarados. Normalmente, los métodos static virtual
declaran que una implementación debe definir un conjunto de operadores
sobrecargados.
Interfaz de ejemplo
C#
interface ISampleInterface
void SampleMethod();
void ISampleInterface.SampleMethod()
// Method implementation.
}
obj.SampleMethod();
Una interfaz puede ser un miembro de un espacio de nombres o una clase. Una
declaración de interfaz puede contener declaraciones (firmas sin ninguna
implementación) de los miembros siguientes:
Métodos
Propiedades
Indizadores
Eventos
Constantes
Operadores
Constructor estático.
Tipos anidados
Campos, métodos, propiedades, indizadores y eventos estáticos.
Declaraciones de miembros con la sintaxis de implementación de interfaz explícita.
Modificadores de acceso explícitos (el acceso predeterminado es public).
declarar que los tipos de implementación deben definir operadores u otros miembros
estáticos. Esta característica permite a los algoritmos genéricos especificar el
comportamiento como un número. Puede ver ejemplos de los tipos numéricos en el
entorno de ejecución de .NET, como System.Numerics.INumber<TSelf>. Estas interfaces
definen operadores matemáticos comunes implementados por muchos tipos numéricos.
El compilador debe resolver las llamadas a los métodos static virtual y static
abstract en tiempo de compilación. Los métodos static virtual y static abstract
declarados en interfaces no tienen un mecanismo de distribución en tiempo de
ejecución análogo a los métodos virtual o abstract declarados en clases. En su lugar,
el compilador usa la información de tipos disponible en tiempo de compilación. Por lo
tanto, los métodos static virtual se declaran casi exclusivamente en interfaces
genéricas. Además, la mayoría de las interfaces que declaran métodos static virtual o
static abstract declaran que uno de los parámetros de tipo debe implementar la
interfaz declarada. Por ejemplo, la interfaz INumber<T> declara que T debe implementar
INumber<T> . El compilador usa el argumento de tipo para resolver llamadas a los
) Importante
Puede probar esta característica trabajando con el tutorial sobre miembros abstractos
estáticos en interfaces.
Herencia de interfaz
Es posible que las interfaces no contengan estado de instancia. Aunque los campos
estáticos ahora están permitidos, los campos de instancia no se permiten en las
interfaces. Las propiedades automáticas de instancia no se admiten en las interfaces, ya
que declararían de forma implícita un campo oculto. Esta regla tiene un efecto sutil en
las declaraciones de propiedad. En una declaración de interfaz, el código siguiente no
declara una propiedad implementada automáticamente como hace en un objeto class
o struct . En su lugar, declara una propiedad que no tiene una implementación
predeterminada pero que se debe implementar en cualquier tipo que implemente la
interfaz:
C#
Una interfaz puede heredar de una o varias interfaces base. Cuando una interfaz invalida
un método implementado en una interfaz base, debe usar la sintaxis de implementación
de interfaz explícita.
Cuando una lista de tipos base contiene una clase e interfaces base, la clase base debe
aparecer primero en la lista.
Una clase que implementa una interfaz puede implementar explícitamente miembros de
esa interfaz. A un miembro implementado explícitamente solo se puede tener acceso
mediante una instancia de la interfaz, y no mediante una instancia de la clase. Además,
solo se puede acceder a los miembros de interfaz predeterminados a través de una
instancia de la interfaz.
C#
interface IPoint
// Property signatures:
// Constructor:
X = x;
Y = y;
// Property implementation:
// Property implementation
Math.Sqrt(X * X + Y * Y);
class MainClass
PrintPoint(p);
Vea también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Tipos de referencia
Interfaces
Utilizar propiedades
Utilizar indizadores
Tipos de referencia que aceptan valores
NULL (referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 7 minutos
7 Nota
En este artículo se tratan los tipos de referencia que aceptan valores NULL. También
puede declarar tipos de valor que aceptan valores NULL.
Los tipos de referencia que admiten valores NULL están disponibles en un código que
ha participado en un contexto compatible con los valores NULL. Los tipos de referencia
que aceptan valores NULL, las advertencias de análisis estático NULL y el operador null-
forgiving son características de lenguaje opcionales. Todas están desactivadas de forma
predeterminada. Un contexto que admite valores NULL se controla en el nivel de
proyecto mediante la configuración de compilación o en el código que usa pragmas.
) Importante
Todas las plantillas de proyecto a partir de .NET 6 (C# 10) habilitan el contexto que
admite valores NULL para el proyecto. Los proyectos creados con plantillas
anteriores no incluyen este elemento y estas características están desactivadas a
menos que las habilite en el archivo del proyecto o use pragmas.
Las distinciones entre un tipo de referencia que no acepta valores NULL T y un tipo de
referencia que acepta valores NULL T? se aplican mediante la interpretación del
compilador de las reglas anteriores. Una variable de tipo T y una variable de tipo T? se
representan mediante el mismo tipo .NET. En el ejemplo siguiente se declara una
cadena que no acepta valores NULL y una cadena que acepta valores NULL y luego se
usa el operador null-forgiving para asignar un valor a una cadena que no acepta valores
NULL:
C#
Las variables notNull y nullable se ambas representan con el tipo String. Dado que los
tipos que no aceptan valores NULL y que aceptan valores NULL se almacenan como el
mismo tipo, hay varias ubicaciones en las que no se permite el uso de un tipo de
referencia que acepte valores NULL. En general, un tipo de referencia que acepta valores
NULL no se puede usar como clase base o interfaz implementada. No se puede usar un
tipo de referencia que acepte valores NULL en ninguna expresión de creación de
objetos o de expresión de prueba de tipos. Un tipo de referencia que acepta valores
NULL no puede ser el tipo de una expresión de acceso a miembros. En los siguientes
ejemplos se muestran estas construcciones:
C#
try
Console.WriteLine(nullableString);
Console.WriteLine("error");
Los tipos de referencia que no aceptan valores NULL siempre deben ser seguros para
desreferenciar porque su estado null-state es not-null. Para aplicar esa regla, el
compilador emite advertencias si un tipo de referencia que no acepta valores NULL no
se inicializa en un valor no NULL. Las variables locales deben asignarse allí donde se
declaran. A cada campo se le debe asignar un valor not-null en un inicializador de
campo o en cada constructor. El compilador emite advertencias cuando una referencia
que no acepta valores NULL se asigna a una referencia cuyo estado es maybe-null. Por
lo general, una referencia que no acepta valores NULL es not-null y no se emite ninguna
advertencia cuando se desreferencian esas variables.
7 Nota
Los tipos de referencia que aceptan valores NULL se pueden inicializar o asignar a null .
Por lo tanto, el análisis estático debe determinar que una variable es not-null antes de
desreferenciarla. Si se determina que una referencia que acepta valores NULL es maybe-
null, la asignación a una variable de referencia que no acepta valores NULL genera una
advertencia del compilador. La consulta siguiente muestra ejemplos de estas
advertencias:
C#
this.shortDescription = productDescription;
shortDescription = productDescription;
detailedDescription = details;
return shortDescription;
else
return $"{shortDescription}\n{detailedDescription}";
if (detailedDescription == null)
return shortDescription;
return $"{shortDescription}\n{detailedDescription}";
return shortDescription;
C#
Use void como el tipo de valor devuelto de un método (o una función local) para
especificar que el método no devuelve un valor.
C#
if (numbers is null)
return;
También puede usar void como un tipo de referente para declarar un puntero a un tipo
desconocido. Para obtener más información, vea Tipos de puntero.
Vea también
Referencia de C#
System.Void
Instrucciones de declaración
Artículo • 18/02/2023 • Tiempo de lectura: 8 minutos
C#
) Importante
Cuando var se usa con tipos de referencia que aceptan valores NULL, siempre
implica un tipo de referencia que acepta valores NULL, aunque el tipo de expresión
no los acepte. El análisis de null-state del compilador protege frente a la
desreferenciación de un posible valor null . Si la variable nunca se asigna a una
expresión que pueda ser NULL, el compilador no emitirá ninguna advertencia. Si
asigna la variable a una expresión que podría ser NULL, debe probar que no sea
NULL antes de desreferenciarla para evitar advertencias.
Un uso común de la palabra clave var es con las expresiones de invocación del
constructor. El uso de var permite no repetir un nombre de tipo en una declaración de
variable y una creación de instancias de objeto, como se muestra en el ejemplo
siguiente:
C#
var xs = new List<int>();
A partir de C# 9.0, se puede usar una expresión new de con tipo de destino como
alternativa:
C#
List<int> xs = new();
List<int>? ys = new();
C#
select word;
Console.WriteLine(s);
C#
C#
Puede acceder a un valor por referencia de la misma manera. En algunos casos, acceder
a un valor por referencia aumenta el rendimiento, ya que evita una operación de copia
potencialmente cara. Por ejemplo, en la instrucción siguiente se muestra cómo es
posible definir un valor local de referencia que se usa para hacer referencia a un valor.
C#
La palabra clave ref se usa antes de la declaración de variable local y antes del valor en
el segundo ejemplo. Si no se incluyen ambas palabras clave ref en la asignación y
declaración de la variable en ambos ejemplos, se produce el error del
compilador CS8172, "No se puede inicializar una variable por referencia con un valor".
C#
En el ejemplo siguiente, se define una clase NumberStore que almacena una matriz de
valores enteros. El método FindNumber devuelve por referencia el primer número que es
mayor o igual que el número que se pasa como argumento. Si ningún número es mayor
o igual que el argumento, el método devuelve el número en el índice 0.
C#
using System;
class NumberStore
C#
value *= 2;
Sin que se admitan los valores devueltos de referencia, este tipo de operación se realiza
al devolver el índice del elemento de matriz junto con su valor. Después, el autor de la
llamada puede usar este índice para modificar el valor en una llamada al método
independiente. En cambio, el autor de la llamada también puede modificar el índice
para tener acceso a otros valores de matriz y, posiblemente, modificarlos.
El siguiente ejemplo muestra cómo se podría reescribir el método FindNumber para usar
la reasignación de variable local ref:
C#
using System;
class NumberStore
ctr--;
Esta segunda versión es más eficaz con secuencias más largas en escenarios donde el
número buscado está más cerca del final de la matriz, ya que en la matriz se itera desde
el final hacia el principio, lo que hace que se examinen menos elementos.
C#
Las declaraciones readonly ref y readonly ref readonly solo son válidas en campos
ref de ref struct .
Vea también
ref (palabra clave)
Cómo evitar asignaciones
Preferencias "var" (reglas de estilo IDE0007 e IDE0008)
Referencia de C#
Relaciones entre tipos en las operaciones de consulta LINQ
C# 11: modificador con ámbito
Tipos integrados (referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
bool System.Boolean
byte System.Byte
sbyte System.SByte
char System.Char
decimal System.Decimal
double System.Double
float System.Single
int System.Int32
uint System.UInt32
nint System.IntPtr
nuint System.UIntPtr
long System.Int64
ulong System.UInt64
short System.Int16
ushort System.UInt16
object System.Object
string System.String
dynamic System.Object
C#
int a = 123;
System.Int32 b = 123;
La palabra clave void representa la ausencia de un tipo. Se usa como el tipo de valor
devuelto de un método que no devuelve un valor.
Vea también
Uso de palabras clave de lenguaje en lugar de nombres de tipo de marco (regla de
estilo IDE0049)
Referencia de C#
Valores predeterminados de los tipos de C#
Tipos no administrados (referencia de
C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
sbyte , byte , short , ushort , int , uint , long , ulong , char , float , double , decimal
o bool
Cualquier tipo enum.
Cualquier tipo pointer.
Cualquier tipo struct definido por el usuario que solo contenga campos de tipos
no administrados.
C#
using System;
public T X;
public T Y;
DisplaySize<Coords<int>>();
DisplaySize<Coords<double>>();
// Output:
Un struct genérico puede ser el origen de los tipos no administrados y de los tipos
construidos administrados. En el ejemplo anterior se define un struct Coords<T>
genérico y se presentan los ejemplos de tipos construidos no administrados. El ejemplo
de un tipo administrado es Coords<object> . Es administrado porque tiene los campos
del tipo object , que está administrado. Si desea que todos los tipos construidos sean
tipos no administrados, use la restricción unmanaged en la definición de un struct
genérico:
C#
public T X;
public T Y;
Vea también
Referencia de C#
Tipos de puntero
Tipos relacionados con el intervalo y la memoria
sizeof (operador)
stackalloc
Valores predeterminados de los tipos de
C# (referencia de C#)
Artículo • 22/02/2023 • Tiempo de lectura: 2 minutos
bool false
struct El valor generado al establecer todos los campos de tipo de valor en sus
valores predeterminados y todos los campos de tipo de referencia en null .
Cualquier tipo de Instancia para la que la propiedad HasValue es false y la propiedad Value
valor que acepta no está definida. Este valor predeterminado también se conoce con el valor
valores NULL null de un tipo de valor que acepta valores NULL.
C#
int a = default(int);
Puede usar el literaldefault para inicializar una variable con el valor predeterminado de
su tipo:
C#
int a = default;
C#
7 Nota
Valores predeterminados
Constructores predeterminados
C# 10: constructores de structs sin parámetros
C# 11: structs predeterminados automáticos
Vea también
Referencia de C#
Constructores
Palabras clave de C#
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Las palabras clave son identificadores reservados predefinidos que tienen un significado
especial para el compilador. No podrá utilizarlos como identificadores en el programa a
no ser que incluyan @ como prefijo. Por ejemplo, @if es un identificador válido, pero
if no lo es, porque if es una palabra clave.
En la primera tabla de este tema se muestran las palabras clave que son identificadores
reservados en cualquier parte de un programa en C#. En la segunda tabla de este tema
se enumeran las palabras clave contextuales en C#. Las palabras clave contextuales
tienen un significado especial solo en un contexto de programa limitado y pueden
utilizarse como identificadores fuera de ese contexto. Por lo general, cuando se agregan
nuevas palabras clave al lenguaje C#, se agregan como palabras clave contextuales para
evitar la interrupción de los programas escritos en versiones anteriores.
abstract
as
base
bool
break
byte
case
catch
char
checked
class
const
continue
decimal
default
delegate
do
double
else
enum
event
explicit
extern
false
finally
fixed
float
for
foreach
goto
if
implicit
in
int
interface
internal
is
lock
long
namespace
new
null
object
operator
out
override
params
private
protected
public
readonly
ref
return
sbyte
sealed
short
sizeof
stackalloc
static
string
struct
switch
this
throw
true
try
typeof
uint
ulong
unchecked
unsafe
ushort
using
virtual
void
volatile
while
add
and
alias
ascending
args
async
await
by
descending
dynamic
equals
from
get
global
group
init
into
join
let
nameof
nint
not
notnull
nuint
on
or
orderby
partial (tipo)
partial (método)
record
remove
select
set
value
var
con
yield
Vea también
Referencia de C#
Modificadores de acceso (Referencia de
C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Los modificadores de acceso son palabras clave que se usan para especificar la
accesibilidad declarada de un miembro o un tipo. En esta sección se presentan los cinco
modificadores de acceso:
public
protected
internal
private
file
Pueden especificarse los siguientes siete niveles de accesibilidad con los modificadores
de acceso:
Niveles de accesibilidad: usar los cuatro modificadores de acceso para declarar seis
niveles de accesibilidad.
Dominio de accesibilidad: especifica en qué secciones del programa se puede
hacer referencia a dicho miembro.
Restricciones en el uso de niveles de accesibilidad: un resumen de las restricciones
sobre usar niveles de accesibilidad declarados.
Vea también
Adición de modificadores de accesibilidad (regla de estilo IDE0040)
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Modificadores de acceso
Palabras clave de acceso
Modificadores
Niveles de accesibilidad (Referencia de
C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Accesibilidad Significado
declarada
protected El acceso está limitado a la clase contenedora o a los tipos derivados de la clase
contenedora.
protected El acceso está limitado al ensamblado actual o a los tipos derivados de la clase
internal contenedora.
private El acceso está limitado a la clase contenedora o a los tipos derivados de la clase
protected contenedora que hay en el ensamblado actual.
Los tipos de nivel superior, que no están anidados en otros tipos, solo pueden tener una
accesibilidad internal o public . La accesibilidad predeterminada para estos tipos es
internal .
Los tipos anidados, que son miembros de otros tipos, pueden tener accesibilidades
declaradas como se indica en la tabla siguiente.
protected
internal
private
protected internal
private protected
protected
internal
private *
protected internal
private protected
internal
private
Consulte también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Modificadores de acceso
Dominio de accesibilidad
Restricciones en el uso de los niveles de accesibilidad
Modificadores de acceso
public
private
protected
internal
Dominio de accesibilidad (Referencia de
C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Ejemplo
Este ejemplo contiene un tipo de nivel superior, T1 , y dos clases anidadas, M1 y M2 . Las
clases contienen campos que tienen diferentes accesibilidades declaradas. En el método
Main , a cada instrucción le sigue un comentario que indica el dominio de accesibilidad
de cada miembro. Observe que las instrucciones que intentan hacer referencia a los
miembros inaccesibles están marcadas con comentarios. Si quiere ver los errores
generados por el compilador cuando se intenta hacer referencia a un miembro
inaccesible, quite los comentarios de uno en uno.
C#
public class T1
static T1()
M1.publicInt = 1;
M1.internalInt = 2;
M2.publicInt = 3;
M2.internalInt = 4;
// in either class:
// M1.privateInt = 2; //CS0122
public class M1
private class M2
class MainClass
// Access is unlimited.
T1.publicInt = 1;
T1.internalInt = 2;
// T1.privateInt = 3;
// Access is unlimited.
T1.M1.publicInt = 1;
T1.M1.internalInt = 2;
// T1.M1.privateInt = 3;
// T1.M2.publicInt = 1;
// T1.M2.internalInt = 2;
// T1.M2.privateInt = 3;
System.Console.ReadKey();
}
Consulte también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Modificadores de acceso
Niveles de accesibilidad
Restricciones en el uso de los niveles de accesibilidad
Modificadores de acceso
public
private
protected
internal
Restricciones en el uso de los niveles de
accesibilidad (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
C#
Context Comentarios
Clases La clase base directa de un tipo de clase debe ser al menos igual de accesible que
el propio tipo de clase.
Interfaces Las interfaces base explícitas de un tipo de interfaz deben ser al menos igual de
accesibles que el propio tipo de interfaz.
Delegados El tipo de valor devuelto y los tipos de parámetros de un tipo de delegado deben
ser al menos igual de accesibles que el propio tipo de delegado.
Constantes El tipo de una constante debe ser al menos igual de accesible que la propia
constante.
Fields El tipo de un campo debe ser al menos igual de accesible que el propio campo.
Métodos El tipo de valor devuelto y los tipos de parámetros de un método deben ser al
menos igual de accesibles que el propio método.
Propiedades El tipo de una propiedad debe ser al menos igual de accesible que la misma
propiedad.
Eventos El tipo de un evento debe ser al menos igual de accesible que el propio evento.
Indizadores Los tipos de parámetro y el tipo de un indexador deben ser al menos igual de
accesibles que el propio indexador.
Context Comentarios
Operadores El tipo de valor devuelto y los tipos de parámetro de un operador deben ser al
menos igual de accesibles que el propio operador.
Constructores Los tipos de parámetro de un constructor deben ser al menos igual de accesibles
que el propio constructor.
Ejemplo
El siguiente ejemplo contiene declaraciones erróneas de diferentes tipos. El comentario
que sigue a cada declaración indica el error del compilador previsto.
C#
using System;
// A delegate:
class B
// A private method:
return 0;
public class A
public B MyMethod()
public B MyProp
set
// protection level."
Console.Write("Compiled successfully");
Consulte también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Modificadores de acceso
Dominio de accesibilidad
Niveles de accesibilidad
Modificadores de acceso
public
private
protected
internal
internal (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Solo se puede tener acceso a los tipos internos o los miembros desde los archivos del
mismo ensamblado, como en este ejemplo:
C#
Para obtener una comparación de internal con los demás modificadores de acceso,
vea Niveles de accesibilidad y Modificadores de acceso.
Es un error hacer referencia a un tipo o miembro con acceso interno fuera del
ensamblado en el que se definió.
Ejemplo 1
Este ejemplo contiene dos archivos, Assembly1.cs y Assembly1_a.cs . El primer archivo
contiene una clase base interna, BaseClass . En el segundo archivo, un intento de crear
una instancia de BaseClass producirá un error.
C#
// Assembly1.cs
C#
// Assembly1_a.cs
class TestAccess
Ejemplo 2
En este ejemplo, use los mismos archivos usados en el ejemplo 1 y cambie el nivel de
accesibilidad de BaseClass a public . Cambie también el nivel de accesibilidad del
miembro intM a internal . En este caso, se puede crear una instancia de la clase, pero
no se puede tener acceso al miembro interno.
C#
// Assembly2.cs
C#
// Assembly2_a.cs
Consulte también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Modificadores de acceso
Niveles de accesibilidad
Modificadores
public
private
protected
private (Referencia de C#)
Artículo • 10/02/2023 • Tiempo de lectura: 2 minutos
Esta página trata sobre el modificador de acceso private . La palabra clave private
también forma parte del modificador de acceso private protected.
El acceso privado es el nivel de acceso menos permisivo. Los miembros privados solo
son accesibles dentro del cuerpo de la clase o el struct en el que se declaran, como en
este ejemplo:
C#
class Employee
Los tipos anidados en el mismo cuerpo también pueden tener acceso a los miembros
privados.
Para obtener una comparación de private con los demás modificadores de acceso, vea
Niveles de accesibilidad y Modificadores de acceso.
Ejemplo
En este ejemplo, la clase Employee contiene dos miembros de datos privados, _name y
_salary . Como miembros privados, solo pueden tener acceso a ellos los métodos de
C#
class Employee2
return _name;
class PrivateTest
// string n = e._name;
// double s = e._salary;
string n = e.GetName();
double s = e.Salary;
Consulte también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Modificadores de acceso
Niveles de accesibilidad
Modificadores
public
protected
internal
protected (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
7 Nota
y private protected.
Para obtener una comparación de protected con los demás modificadores de acceso,
vea Niveles de accesibilidad.
Ejemplo 1
Un miembro protegido de una clase base es accesible en una clase derivada únicamente
si el acceso se produce a través del tipo de clase derivada. Por ejemplo, vea el siguiente
segmento de código:
C#
class A
class B : A
// a.x = 10;
b.x = 10;
Ejemplo 2
En este ejemplo, la clase DerivedPoint se deriva de Point . Por lo tanto, puede acceder a
los miembros protegidos de la clase base directamente desde la clase derivada.
C#
class Point
protected int x;
protected int y;
dpoint.x = 10;
dpoint.y = 15;
// Output: x = 10, y = 15
Consulte también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Modificadores de acceso
Niveles de accesibilidad
Modificadores
public
private
internal
Security concerns for internal virtual keywords (Problemas de seguridad de
palabras clave virtuales internas)
public (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
C#
class SampleClass
Ejemplo
En el ejemplo siguiente, se declaran dos clases, PointTest y Program . Se obtiene acceso
a los miembros públicos x e y de PointTest directamente desde Program .
C#
class PointTest
public int x;
public int y;
class Program
p.x = 10;
p.y = 15;
// Output: x = 10, y = 15
Consulte también
Referencia de C#
Guía de programación de C#
Modificadores de acceso
Palabras clave de C#
Modificadores de acceso
Niveles de accesibilidad
Modificadores
private
protected
internal
protected internal (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Ejemplo
Se puede obtener acceso a un miembro protected internal de una clase base desde
cualquier tipo de ensamblado que lo contenga. También estará accesible en una clase
derivada ubicada en otro ensamblado, pero solo si el acceso se produce a través de una
variable del tipo de clase derivada. Por ejemplo, vea el siguiente segmento de código:
C#
// Assembly1.cs
class TestAccess
void Access()
baseObject.myValue = 5;
C#
// Assembly2.cs
// baseObject.myValue = 10;
derivedObject.myValue = 10;
Los miembros de struct no pueden ser protected internal , porque los structs no se
heredan.
Consulte también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Modificadores de acceso
Niveles de accesibilidad
Modificadores
public
private
internal
Security concerns for internal virtual keywords (Problemas de seguridad de
palabras clave virtuales internas)
private protected (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
7 Nota
Ejemplo
Se puede tener acceso a un miembro private protected de una clase base desde tipos
derivados en el ensamblado que lo contiene solo si el tipo estático de la variable es el
tipo de clase derivada. Por ejemplo, vea el siguiente segmento de código:
C#
void Access()
// baseObject.myValue = 5;
myValue = 5;
C#
// Assembly2.cs
void Access()
// myValue = 10;
tener acceso de dos maneras. El primer intento de acceso a myValue a través de una
instancia de BaseClass generará un error. En cambio, el intento de usarlo como un
miembro heredado en DerivedClass1 se realizará correctamente.
Los miembros de struct no pueden ser private protected , porque los structs no se
heredan.
Consulte también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Modificadores de acceso
Niveles de accesibilidad
Modificadores
public
private
internal
Security concerns for internal virtual keywords (Problemas de seguridad de
palabras clave virtuales internas)
abstract (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
Ejemplo 1
En este ejemplo, la clase Square debe proporcionar una implementación de GetArea
porque se deriva de Shape :
C#
Una clase no abstracta que derive de una clase abstracta debe incluir
implementaciones reales de todos los descriptores de acceso y métodos
abstractos heredados.
C#
Las propiedades abstractas se comportan como métodos abstractos, salvo por las
diferencias en la sintaxis de declaración e invocación.
Para obtener más información sobre las clases abstractas, vea Clases y miembros de
clase abstractos y sellados (Guía de programación de C#).
Una clase abstracta debe proporcionar implementación para todos los miembros de
interfaz.
Una clase abstracta que implemente una interfaz podría asignar los métodos de interfaz
a métodos abstractos. Por ejemplo:
C#
interface I
void M();
}
abstract class C : I
Ejemplo 2
En este ejemplo, la clase DerivedClass se deriva de una clase abstracta BaseClass . La
clase abstracta contiene un método abstracto, AbstractMethod , y dos propiedades
abstractas, X y Y .
C#
// Abstract class
// Abstract method
// Abstract properties
_x++;
_y++;
get
return _x + 10;
get
return _y + 10;
o.AbstractMethod();
En el ejemplo anterior, si intenta crear una instancia de la clase abstracta mediante una
instrucción como esta:
C#
Consulte también
Referencia de C#
Guía de programación de C#
Modificadores
virtual
override
Palabras clave de C#
async (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 4 minutos
Use el modificador async para especificar que un método, una expresión lambda o un
método anónimo es asincrónico. Si usa este modificador en un método o una expresión,
se hace referencia al mismo como un método asincrónico. En el ejemplo siguiente se
define un método asincrónico denominado ExampleMethodAsync :
C#
//...
C#
(nivel 1) CS4014.
La palabra clave async es contextual en el sentido de que es una palabra clave cuando
modifica un método, una expresión lambda o un método anónimo. En todos los demás
contextos, se interpreta como identificador.
Ejemplo
En el ejemplo siguiente se muestra la estructura y el flujo de control entre un
controlador de eventos asincrónicos, StartButton_Click , y un método asincrónico,
ExampleMethodAsync . El resultado del método asincrónico es el número de caracteres de
una página web. El código es adecuado para una aplicación Windows Presentation
Foundation (WPF) o de la Tienda Windows creada en Visual Studio; vea los comentarios
del código para configurar la aplicación.
Puede ejecutar este código en Visual Studio como una aplicación Windows Presentation
Foundation (WPF) o una aplicación de la Tienda Windows. Necesita un control de botón
denominado StartButton y un control de cuadro de texto denominado ResultsTextBox .
Recuerde establecer los nombres y el controlador de manera que tenga algo similar a
esto:
XAML
Click="StartButton_Click" Name="StartButton"/>
C#
ResultsTextBox.Text += "\n";
try
catch (Exception)
// integer result.
return exampleInt;
// Length: 53292
) Importante
Para obtener más información sobre las tareas y el código que se ejecuta mientras
se espera la finalización de una tarea, vea Programación asincrónica con async y
await. Para ver un ejemplo completo de la consola que usa elementos similares,
consulte el artículo Iniciar varias tareas asincrónicas y procesarlas a medida que se
completan (C#).
Task
Task<TResult>
void. Los métodos async void no suelen ser recomendables para código que no
sea controladores de eventos dado que los autores de la llamada no pueden usar
await con esos métodos y deben implementar otro mecanismo para informar
El método asincrónico no puede declarar ningún parámetro in, ref o out, ni puede tener
un valor devuelto de referencia, pero puede llamar a los métodos que tienen estos
parámetros.
método inicia.
Devuelve otro tipo, normalmente un tipo de valor, que tiene un método GetAwaiter
para minimizar las asignaciones de memoria en secciones críticas de rendimiento del
código.
Vea también
AsyncStateMachineAttribute
await
Programación asincrónica con async y await
Procesamiento de tareas asincrónicas a medida que se completan
const (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
La palabra clave const se usa para declarar un campo constante o una local constante.
Los campos y locales constantes no son variables y no se pueden modificar. Las
constantes pueden ser números, valores booleanos, cadenas o una referencia nula. No
cree una constante para representar información que esperas que cambie en algún
momento. Por ejemplo, no use un campo constante para almacenar el precio de un
servicio, un número de versión de producto o el nombre comercial de una compañía.
Estos valores pueden cambiar con el tiempo y, como los compiladores propagan las
constantes, otro código compilado con sus bibliotecas tendrán que volver a compilarse
para ver los cambios. Vea también la palabra clave readonly. Por ejemplo:
C#
const int X = 0;
A partir de C# 10, las cadenas interpoladas pueden ser constantes, si todas las
expresiones utilizadas también son cadenas constantes. Esta característica puede
mejorar el código que compila cadenas constantes:
C#
Observaciones
El tipo de una declaración constante especifica el tipo de los miembros que la
declaración presenta. El inicializador de una local constante o de un campo constante
debe ser una expresión constante que se pueda convertir implícitamente al tipo de
destino.
Una expresión constante es una expresión que se puede evaluar por completo en
tiempo de compilación. Por lo tanto, los únicos valores posibles para las constantes de
tipos de referencia son string y una referencia nula.
La declaración de constante puede declarar varias constantes, tales como:
C#
C#
7 Nota
La palabra clave readonly difiere de la palabra clave const . Un campo const solo
se puede inicializar en la declaración del campo. Un campo readonly se puede
inicializar en la declaración o en un constructor. Por lo tanto, los campos readonly
pueden tener diferentes valores en función del constructor que se use. Además,
aunque un campo const es una constante en tiempo de compilación, el campo
readonly se puede usar para constantes en tiempo de ejecución, como en esta
Ejemplos
C#
class SampleClass
public int x;
public int y;
x = p1;
y = p2;
/* Output
x = 11, y = 22
C1 = 5, C2 = 10
*/
Este ejemplo demuestra cómo usar las constantes como variables locales.
C#
Consulte también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Modificadores
readonly
event (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
La palabra clave event se usa para declarar un evento en una clase de publicador.
Ejemplo
En el ejemplo siguiente se muestra cómo declarar y generar un evento que usa
EventHandler como el tipo de delegado subyacente. Para obtener el código de ejemplo
completo que también muestra cómo usar el tipo delegado EventHandler<TEventArgs>
genérico y cómo suscribirse a un evento y crear un método de controlador de evento,
vea Procedimiento para publicar eventos que cumplan las directrices de .NET.
C#
Los eventos son un tipo especial de delegado de multidifusión que solo se pueden
invocar desde la clase (o clases derivadas) o el struct en el que se declaran (la clase de
publicador). Si otras clases o structs se suscriben al evento, se llamará a sus métodos de
controlador de eventos cuando la clase de publicador genera el evento. Para más
información y ejemplos de código, vea Eventos y Delegados.
Las constantes pueden marcarse como public, private, protected, internal, protected
internal o private protected. Estos modificadores de acceso definen cómo los usuarios
de la clase pueden obtener acceso al evento. Para obtener más información, consulte
Modificadores de acceso.
static Hace que el evento esté disponible para los llamadores en cualquier Clases
momento, aunque no exista ninguna instancia de la clase. estáticas y
sus
miembros
Un evento puede declararse como evento estático mediante la palabra clave static. Esto
hace que el evento esté disponible para los llamadores en cualquier momento, aunque
no exista ninguna instancia de la clase. Para más información, vea Clases estáticas y sus
miembros.
Un evento puede marcarse como virtual mediante la palabra clave virtual. Esto permite
que las clases derivadas invaliden el comportamiento de eventos mediante la palabra
clave override. Para obtener más información, vea Herencia. Un evento que reemplaza
un evento virtual también puede ser sealed, que especifica que ya no es virtual para las
clases derivadas. Por último, se puede declarar un evento como abstract, lo que significa
que el compilador no generará los bloques de descriptor de acceso de eventos add y
remove . Por tanto, las clases derivadas deben proporcionar una implementación propia.
C#
[DllImport("avifil32.dll")]
La palabra clave extern también puede definir un alias del ensamblado externo, lo que
permite hacer referencia a diferentes versiones del mismo componente desde un único
ensamblado. Para obtener más información, vea alias externo.
Es un error usar los modificadores abstract y extern juntos para modificar el mismo
miembro. El uso del modificador extern significa que el método se implementa fuera
del código de C#, mientras que el uso del modificador abstract significa que la
implementación del método no se proporciona en la clase.
La palabra clave extern tiene usos más limitados en C# que en C++. Para comparar la
palabra clave de C# con la de C++, consulte el tema sobre el uso de extern para
especificar vinculación en la referencia del lenguaje C++.
Ejemplo 1
En este ejemplo, el programa recibe una cadena del usuario y la muestra en un cuadro
de mensaje. El programa usa el método MessageBox importado de la biblioteca
User32.dll.
C#
//using System.Runtime.InteropServices;
class ExternTest
[DllImport("User32.dll", CharSet=CharSet.Unicode)]
string myString;
myString = Console.ReadLine();
Ejemplo 2
En este ejemplo se muestra un programa de C# que llama a una biblioteca de C (una
DLL nativa).
// cmdll.c
return i*10;
2. Abra una ventana del símbolo del sistema de las herramientas nativas de Visual
Studio x64 (o x32) desde el directorio de instalación de Visual Studio y compile el
archivo cmdll.c escribiendo cl -LD cmdll.c en el símbolo del sistema.
C#
// cm.cs
using System;
using System.Runtime.InteropServices;
[DllImport("Cmdll.dll")]
4. Abra una ventana del símbolo del sistema de las herramientas nativas de Visual
Studio x64 (o x32) del directorio de instalación de Visual Studio y compile el
archivo cm.cs escribiendo:
csc cm.cs (para el símbolo del sistema x64), o bien csc -platform:x86 cm.cs
(para el símbolo del sistema x32)
Resultados
Consulte también
System.Runtime.InteropServices.DllImportAttribute
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Modificadores
in (Modificador genérico) (Referencia de
C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Para los parámetros de tipo genérico, la palabra clave in especifica que el parámetro de
tipo es contravariante. Puede usar la palabra clave in en las interfaces y delegados
genéricos.
Una interfaz que tiene un parámetro de tipo contravariante permite que sus métodos
acepten argumentos de tipos menos derivados que los que se especifican en el
parámetro de tipo de interfaz. Por ejemplo, en la interfaz IComparer<T>, el tipo T es
contravariante, puede asignar un objeto de tipo IComparer<Person> a un objeto de tipo
IComparer<Employee> sin tener que usar ningún método de conversión especial si
A un delegado contravariante se le puede asignar otro delegado del mismo tipo, pero
con un parámetro de tipo genérico menos derivado.
C#
// Contravariant interface.
class Program
istr = iobj;
C#
// Contravariant delegate.
{ }
{ }
dButton = dControl;
dButton(new Button());
Vea también
out
Covarianza y contravarianza
Modificadores
new (Modificador, Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
Cuando se utiliza como modificador de una declaración, la palabra clave new oculta
explícitamente un miembro heredado de una clase base. Cuando se oculta un miembro
heredado, la versión derivada del miembro reemplaza a la versión de la clase base. Esto
supone que la versión de clase base del miembro es visible, ya que ya estaría oculta si se
hubiera marcado como private o, en algunos casos, como internal . Aunque los
miembros public o protected se pueden ocultar sin utilizar el modificador new , se
generará una advertencia del compilador. Si utiliza new explícitamente para ocultar un
miembro, se suprime esta advertencia.
También puede usar la palabra clave new para crear una instancia de un tipo o como
una restricción de tipo genérico.
Para ocultar un miembro heredado, declárelo en la clase derivada con el mismo nombre
de miembro y modifíquelo con la palabra clave new . Por ejemplo:
C#
public int x;
La ocultación de nombres por medio de la herencia toma una de las siguientes formas:
Es un error usar new y override en el mismo miembro, porque los dos modificadores
tienen significados mutuamente excluyentes. El modificador new crea un nuevo
miembro con el mismo nombre y oculta el miembro original. El modificador override
amplía la implementación de un miembro heredado.
Ejemplos
En este ejemplo, una clase base, BaseC , y una clase derivada, DerivedC , utilizan el mismo
nombre de campo x , lo que oculta el valor del campo heredado. El ejemplo muestra el
uso del modificador new . También muestra cómo obtener acceso a los miembros
ocultos de la clase base mediante sus nombres completos.
C#
Console.WriteLine(x);
Console.WriteLine(BaseC.x);
Console.WriteLine(y);
/*
Output:
100
55
22
*/
En este ejemplo, una clase anidada oculta una clase que tiene el mismo nombre en la
clase base. El ejemplo muestra cómo utilizar el modificador new para eliminar el
mensaje de advertencia y cómo obtener acceso a los miembros de la clase oculta
mediante sus nombres completos.
C#
public int y;
public int y;
public int z;
Console.WriteLine(c1.x);
Console.WriteLine(c2.x);
/*
Output:
100
200
*/
text
Vea también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Modificadores
Control de versiones con las palabras clave Override y New
Saber cuándo utilizar las palabras clave Override y New
out (Modificador genérico) (Referencia
de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Para los parámetros de tipo genérico, la palabra clave out especifica que el parámetro
de tipo es covariante. Puede usar la palabra clave out en las interfaces y delegados
genéricos.
Una interfaz con un parámetro de tipo covariante permite que sus métodos devuelvan
tipos más derivados que los especificados por el parámetro de tipo. Por ejemplo, dado
que en .NET Framework 4, en IEnumerable<T>, el tipo T es covariante, puede asignar un
objeto del tipo IEnumerable(Of String) a otro objeto del tipo IEnumerable(Of Object)
sin usar ningún método de conversión especial.
A un delegado covariante se le puede asignar otro delegado del mismo tipo, pero con
un parámetro de tipo genérico más derivado.
C#
// Covariant interface.
class Program
iobj = istr;
7 Nota
Hay una excepción para esta regla. Si en una interfaz covariante tiene un
delegado genérico contravariante como parámetro de método, puede usar el
tipo covariante como parámetro de tipo genérico para este delegado. Para
obtener más información sobre los delegados genéricos covariantes y
contravariantes, vea Varianza en delegados y Usar la varianza para los
delegados genéricos Func y Action.
C#
// Covariant delegate.
dControl = dButton;
dControl();
Vea también
Varianza en interfaces genéricas
in
Modificadores
override (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
C#
No se pueden usar los modificadores new , static o virtual para modificar un método
override .
Para obtener más información sobre cómo usar la palabra clave override , vea Control
de versiones con las palabras clave Override y New y Saber cuándo usar las palabras
clave Override y New (Guía de programación de C#). Para obtener información sobre la
herencia, vea Herencia.
Ejemplo
En este ejemplo se define una clase base denominada Employee y una clase derivada
denominada SalesEmployee . La clase SalesEmployee incluye un campo adicional,
salesbonus , e invalida el método CalculatePay para tenerlo en cuenta.
C#
class TestOverride
Name = name;
_basepay = basepay;
return _basepay;
: base(name, basepay)
_salesbonus = salesbonus;
/*
Output:
*/
Para obtener más información sobre tipos de valor devuelto covariantes, vea la nota de
propuesta de características.
Vea también
Referencia de C#
Herencia
Palabras clave de C#
Modificadores
abstract
virtual
new (modificador)
Polimorfismo
readonly (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 4 minutos
2 Advertencia
En una devolución del método ref readonly, el modificador readonly indica que el
método devuelve una referencia y las operaciones de escritura no se permiten en
esa referencia.
Ejemplo de campo readonly
En este ejemplo, el valor del campo year no se puede cambiar en el método
ChangeYear , aunque se asigne un valor en el constructor de clase:
C#
class Age
Age(int year)
_year = year;
void ChangeYear()
C#
Estos contextos de constructor son también los únicos en los que es válido pasar un
campo readonly como parámetro out o ref.
7 Nota
C#
C#
public int x;
public SamplePoint()
z = 24;
x = p1;
y = p2;
z = p3;
p2.x = 55; // OK
/*
Output:
*/
C#
C#
No es necesario que el tipo devuelto sea una readonly struct . Cualquier tipo que
pueda devolver ref también puede devolver ref readonly .
Vea también
Agregar modificador readonly (regla de estilo IDE0044)
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Modificadores
const
Campos
sealed (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Cuando se aplica a una clase, el modificador sealed impide que otras clases hereden de
ella. En el ejemplo siguiente, la clase B hereda de la clase A , pero ninguna clase puede
heredar de la clase B .
C#
class A {}
sealed class B : A {}
Ejemplo
En el ejemplo siguiente, Z hereda de Y pero Z no puede invalidar la función virtual F
que se declara en X y se sella en Y .
C#
class X
class Y : X
class Z : Y
// Overriding F2 is allowed.
Al definir nuevos métodos o propiedades en una clase, puede impedir que las clases
derivadas los invaliden. Para ello, no los declare como virtuales.
Es un error usar el modificador abstract con una clase sellada, porque una clase
abstracta debe heredarla una clase que proporcione una implementación de los
métodos o propiedades abstractos.
Para obtener más ejemplos, vea Clases y miembros de clase abstractos y sellados.
C#
public int x;
public int y;
class SealedTest2
sc.x = 110;
sc.y = 150;
Comentarios
Para determinar si se debe sellar una clase, un método o una propiedad, por lo general
debe tener en cuenta los dos puntos siguientes:
Las posibles ventajas que podrían obtener las clases derivadas con la capacidad de
personalizar la clase.
La posibilidad de que las clases derivadas modifiquen las clases de tal manera que
no funcionen correctamente o del modo esperado.
Consulte también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Clases estáticas y sus miembros
Clases y miembros de clase abstractos y sellados
Modificadores de acceso
Modificadores
override
virtual
static (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
En esta página se trata la palabra clave del modificador static . La palabra clave static
también forma parte de la directiva using static.
Use el modificador static para declarar un miembro estático, que pertenece al propio
tipo en lugar de a un objeto específico. El modificador static se puede usar para
declarar clases static . En las clases, las interfaces y las estructuras, puede agregar el
modificador static a los campos, los métodos, las propiedades, los operadores, los
eventos y los constructores. El modificador static no se puede usar con indizadores ni
finalizadores. Para más información, vea Clases estáticas y sus miembros.
Puede agregar el modificador static a una función local. Una función local estática no
puede capturar variables locales o el estado de la instancia.
C#
C#
C#
Console.WriteLine(MyBaseC.MyStruct.x);
Mientras que una instancia de una clase contiene una copia independiente de todos los
campos de instancia de la clase, solo hay una copia de cada campo static .
No se puede usar this para hacer referencia a métodos static o descriptores de acceso
de propiedades.
Si la palabra clave static se aplica a una clase, todos los miembros de esta deben ser
static .
Las clases, las interfaces y las clases static pueden tener constructores static . Se llama
a un constructor static en algún momento entre el inicio del programa y la creación de
una instancia de la clase.
7 Nota
La palabra clave static tiene usos más limitados que en C++. Para ver una
comparación con la palabra clave de C++, vea Clases de almacenamiento (C++).
Para mostrar miembros static , es recomendable una clase que represente al empleado
de una empresa. Supongamos que la clase contiene un método de recuento de
empleados y un campo para almacenar el número de empleados. El método y el campo
no pertenecen a ninguna instancia de ningún empleado, sino que pertenecen a la clase
de empleados en su conjunto. Se deben declarar como miembros static de la clase.
public Employee4()
this.name = name;
this.id = id;
return ++employeeCounter;
string id = Console.ReadLine();
string n = Console.ReadLine();
Employee4.employeeCounter = Int32.Parse(n);
Employee4.AddEmployee();
Console.WriteLine($"Name: {e.name}");
Console.WriteLine($"ID: {e.id}");
/*
Input:
Matthias Berndt
AF643G
15
Sample Output:
ID: AF643G
*/
C#
class Test
static int x = y;
static int y = 5;
Console.WriteLine(Test.x);
Console.WriteLine(Test.y);
Test.x = 99;
Console.WriteLine(Test.x);
/*
Output:
99
*/
Consulte también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Modificadores
using static (directiva)
Clases estáticas y sus miembros
unsafe (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
La palabra clave unsafe denota un contexto no seguro, que es necesario para realizar
cualquier operación que implique punteros. Para obtener más información, vea Código
no seguro y punteros (Guía de programación de C#).
C#
El ámbito del contexto no seguro se extiende desde la lista de parámetros hasta el final
del método, por lo que también pueden usarse punteros en la lista de parámetros:
C#
unsafe static void FastCopy ( byte* ps, byte* pd, int count ) {...}
También puede usarse un bloque no seguro para habilitar el uso de código no seguro
en el bloque. Por ejemplo:
C#
unsafe
Ejemplo
C#
// compile with: -unsafe
class UnsafeTest
*p *= *p;
int i = 5;
SquarePtrParam(&i);
Console.WriteLine(i);
// Output: 25
Vea también
Referencia de C#
Palabras clave de C#
Instrucción fixed
Código no seguro, tipos de puntero y punteros de función
Operadores relacionados con el puntero
virtual (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
La palabra clave virtual se usa para modificar una declaración de método, propiedad,
indizador o evento y permitir que se invalide en una clase derivada. Por ejemplo,
cualquier clase que herede este método puede reemplazarlo:
C#
return x * y;
Observaciones
Cuando se invoca a un método virtual, se busca un miembro de reemplazo en el tipo en
tiempo de ejecución del objeto. Se llama al miembro de reemplazo en la clase más
derivada, que podría ser el miembro original si ninguna clase derivada ha invalidado al
miembro.
C#
class MyBaseClass
get
return _name;
set
if (!string.IsNullOrEmpty(value))
_name = value;
else
_name = "Unknown";
Las propiedades virtuales se comportan como métodos virtuales, salvo por las
diferencias en la sintaxis de declaración e invocación.
Una propiedad virtual heredada se puede invalidar en una clase derivada al incluir
una declaración de propiedad que use el modificador override .
Ejemplo
En este ejemplo, la clase Shape contiene las dos coordenadas x , y y el método virtual
Area() . Las distintas clases de formas como Circle , Cylinder y Sphere heredan la clase
Shape y se calcula el área de cada figura. Cada clase derivada tiene su propia
Observe que las clases heredadas Circle , Sphere y Cylinder usan constructores que
inicializan la clase base, como se muestra en la siguiente declaración.
C#
C#
class TestClass
public Shape()
_x = x;
_y = y;
return _x * _y;
return PI * _x * _x;
return 4 * PI * _x * _x;
return 2 * PI * _x * _x + 2 * PI * _x * _y;
// Display results.
/*
Output:
*/
Vea también
Polimorfismo
abstract
override
new (modificador)
volatile (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
La palabra clave volatile indica que un campo puede ser modificado por varios
subprocesos que se ejecutan al mismo tiempo. El compilador, el sistema de runtime e
incluso el hardware pueden reorganizar las lecturas y escrituras en las ubicaciones de
memoria por motivos de rendimiento. Los campos declarados como volatile se
excluyen de determinados tipos de optimizaciones. No hay ninguna garantía de una
única ordenación total de las operaciones de escritura volátiles como se muestra en
todos los subprocesos de ejecución. Para obtener más información, vea la clase Volatile.
7 Nota
Tipos de referencia.
Tipos de puntero (en un contexto no seguro). Observe que aunque el propio
puntero puede ser volatile, no el objeto al que apunta. Es decir, no puede declarar
un "puntero a volatile".
Tipos simples como sbyte , byte , short , ushort , int , uint , char , float y bool .
Un tipo enum con uno de los siguientes tipos base: byte , sbyte , short , ushort ,
int o uint .
Otros tipos, incluidos double y long , no pueden marcarse como volatile porque no se
puede garantizar que las lecturas y escrituras los campos de esos tipos sean atómicas.
Para proteger el acceso multiproceso a esos tipos de campos, use los miembros de clase
Interlocked o proteja el acceso mediante la instrucción lock.
La palabra clave volatile solo se puede aplicar a campos de class o struct . Las
variables locales no se pueden declarar como volatile .
Ejemplo
En el ejemplo siguiente se muestra cómo declarar una variable de campo pública como
volatile .
C#
class VolatileTest
sharedStorage = i;
C#
while (!_shouldStop)
_shouldStop = true;
// Create the worker thread object. This does not start the thread.
workerThread.Start();
while (!workerThread.IsAlive)
Thread.Sleep(500);
workerObject.RequestStop();
workerThread.Join();
// Sample output:
Vea también
Especificación del lenguaje C#: palabra clave volatile
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Modificadores
lock (Instrucción)
Interlocked
Palabras clave de instrucciones
(Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Consulte también
Referencia de C#
Instrucciones
Palabras clave de C#
throw (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
Observaciones
La sintaxis de throw es la siguiente:
C#
throw [e];
C#
using System;
namespace Throw2
return numbers[index];
C#
using System;
try
Console.WriteLine($"Retrieved {value}");
catch (IndexOutOfRangeException e)
C#
using System;
namespace Throw
public Sentence(string s)
Value = s;
try
return Value[0];
catch (NullReferenceException e)
throw;
// at Sentence.GetFirstCharacter()
// at Example.Main()
) Importante
También puede usar la sintaxis throw e en un bloque catch para crear instancias
de una nueva excepción que se pase al autor de llamada. En este caso, no se
conserva el seguimiento de la pila de la excepción original, que está disponible en
la propiedad StackTrace.
La expresión throw
Se puede usar throw como una expresión y como una instrucción. Esto permite iniciar
una excepción en contextos que antes no se admitían. Entre ellas se incluyen las
siguientes:
C#
private static void DisplayFirstNumber(string[] args)
else
C#
C#
Vea también
Preferencias de "throw" (regla de estilo IDE0016)
Referencia de C#
Guía de programación de C#
try-catch
Palabras clave de C#
Cómo: Iniciar excepciones explícitamente
try-catch (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 9 minutos
La instrucción try-catch consta de un bloque try seguido de una o más cláusulas catch
que especifican controladores para diferentes excepciones.
El bloque try contiene el código protegido que puede producir la excepción. El bloque
se ejecuta hasta que se produce una excepción o hasta que se completa correctamente.
Por ejemplo, el intento siguiente de convertir un objeto null produce la excepción
NullReferenceException:
C#
object o2 = null;
try
Aunque la cláusula catch puede utilizarse sin argumentos para detectar cualquier tipo
de excepción, no se recomienda este uso. En general, solo debe convertir las
excepciones que sabe cómo recuperar. Por lo tanto, debe especificar siempre un
argumento de objeto derivado de System.Exception. El tipo de excepción debe ser lo
más específico posible para evitar que se acepten incorrectamente excepciones que el
controlador de excepciones no pueda resolver realmente. Por lo tanto, se prefieren
excepciones concretas sobre el tipo Exception base. Por ejemplo:
C#
catch (InvalidCastException e)
Es posible utilizar más de una cláusula catch específica en la misma instrucción try-
catch. En este caso, el orden de las cláusulas catch es importante, puesto que las
cláusulas catch se examinan por orden. Detectar las excepciones más específicas antes
que las menos específicas. El compilador genera un error si ordena los bloques de
detección para que un bloque posterior nunca pueda alcanzarse.
La utilización de los argumentos catch es una manera de filtrar las excepciones que
desea controlar. También se puede usar una expresión de filtro que examine aún más la
excepción para decidir si controlarla. Si la expresión de filtro devuelve false, prosigue la
búsqueda de un controlador.
C#
Los filtros de excepción son preferibles para detectar y volver a producir (se explica a
continuación) porque los filtros dejan la pila intacta. Si un controlador posterior vuelca
la pila, puede ver la procedencia original de la excepción, más que solo la ubicación en
la que se volvió a producir. Un uso común de las expresiones de filtro de excepciones es
el registro. Puede crear una función de filtro que siempre devuelva false y que también
resulte en un registro, o bien puede registrar excepciones a medida que se produzcan
sin tener que controlarlas y volver a generarlas.
Se puede usar una instrucción throw en un bloque catch para volver a iniciar la
excepción detectada por la instrucción catch . En el ejemplo siguiente se extrae
información de origen de una excepción IOException y, a continuación, se produce la
excepción al método principal.
C#
catch (FileNotFoundException e)
catch (IOException e)
throw;
C#
catch (InvalidCastException e)
También se puede volver a producir una excepción sin una condición específica es true,
tal y como se muestra en el ejemplo siguiente.
C#
catch (InvalidCastException e)
if (e.Data == null)
{
throw;
else
7 Nota
C#
Desde dentro de un bloque try , solo deben inicializarse las variables que se declaran en
el mismo. De lo contrario, puede ocurrir una excepción antes de que se complete la
ejecución del bloque. Por ejemplo, en el siguiente ejemplo de código, la variable n se
inicializa dentro del bloque try . Un intento de utilizar esta variable fuera del bloque
try en la instrucción Write(n) generará un error del compilador.
C#
int n;
try
n = 123;
catch
Console.Write(n);
Una tarea puede encontrarse en un estado de error debido a que ocurrieron varias
excepciones en el método asincrónico esperado. Por ejemplo, la tarea podría ser el
resultado de una llamada a Task.WhenAll. Cuando espera una tarea de este tipo, solo se
captura una de las excepciones y no puede predecir qué excepción se capturará. Para
obtener un ejemplo, vea la sección Ejemplo de Task.WhenAll.
Ejemplo
En el ejemplo siguiente, el bloque try contiene una llamada al método ProcessString
que puede causar una excepción. La cláusula catch contiene el controlador de
excepciones que muestra un mensaje en la pantalla. Cuando la instrucción throw se
llama desde dentro ProcessString , el sistema busca la instrucción catch y muestra el
mensaje Exception caught .
C#
class TryFinallyTest
if (s == null)
try
ProcessString(s);
catch (Exception e)
/*
Output:
* */
C#
class ThrowTest3
if (s == null)
try
string s = null;
ProcessString(s);
// Most specific:
catch (ArgumentNullException e)
// Least specific:
catch (Exception e)
/*
Output:
*/
C#
try
if (theTask.Exception != null)
+ theTask.Exception.Message);
+ theTask.Exception.InnerException.Message);
await Task.Delay(100);
return "Done";
// Result: Done
Ejemplo de Task.WhenAll
En el ejemplo siguiente se muestra el control de excepciones en el que varias tareas
pueden producir varias excepciones. El bloque try espera la tarea devuelta por una
llamada a Task.WhenAll. La tarea se completa cuando se hayan completado las tres
tareas a las que se aplica el método WhenAll.
Cada una de las tres tareas produce una excepción. El bloque catch se itera a través de
las excepciones, que se encuentran en la propiedad Exception.InnerExceptions de la
tarea devuelta por Task.WhenAll.
C#
try
await allTasks;
await Task.Delay(100);
// Output:
Vea también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Instrucciones try, throw y catch (C++)
throw
try-finally
Cómo: Iniciar excepciones explícitamente
FirstChanceException
UnhandledException
try-finally (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
Mediante el uso de un bloque finally , puede limpiar todos los recursos asignados en
un bloque try y ejecutar código incluso si se produce una excepción en el bloque try .
Normalmente, las instrucciones de un bloque finally se ejecutan cuando el control
abandona una instrucción try . La transferencia de control se puede producir como
resultado de la ejecución normal, de la ejecución de una instrucción break , continue ,
goto o return , o de la propagación de una excepción fuera de la instrucción try .
excepción. Esto, a su vez, depende de cómo esté configurado el equipo. Los únicos
casos en los que no se ejecutan las cláusulas finally implican que un programa se
detenga inmediatamente. Un ejemplo de esto sería cuando se produce
InvalidProgramException debido a que las instrucciones IL están dañadas. En la mayoría
de los sistemas operativos, la limpieza de recursos razonable tendrá lugar como parte
de la detención y descarga del proceso.
Ejemplo
En el ejemplo siguiente, una instrucción de conversión no válida provoca una excepción
System.InvalidCastException . Se trata de una excepción no controlada.
C#
int i = 123;
object obj = s;
try
i = (int)obj;
finally
// Output:
//
// i = 123
C#
try
TryCast();
Console.WriteLine
ex.GetType());
// it on.
throw;
int i = 123;
object obj = s;
try
i = (int)obj;
finally
// Report that the finally block is run, and show that the value
of
// Output:
C# también contiene la instrucción using, que proporciona una función similar para
objetos IDisposable en una sintaxis adecuada.
Vea también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Instrucciones try, throw y catch (C++)
throw
try-catch
Cómo: Iniciar excepciones explícitamente
try-catch-finally (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Para más información y ejemplos sobre cómo volver a iniciar excepciones, vea try-catch
y Generación de excepciones. Para más información sobre el bloque finally , vea try-
finally.
Ejemplo
C#
// To run this code, substitute a valid path from your local machine
try
catch (System.IO.IOException e)
finally
if (file != null)
file.Close();
Vea también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Instrucciones try, throw y catch (C++)
throw
Cómo: Iniciar excepciones explícitamente
using (instrucción)
instrucciones activadas y desactivadas
(referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
C#
uint a = uint.MaxValue;
unchecked
try
checked
Console.WriteLine(a + 1);
catch (OverflowException e)
7 Nota
Para obtener más información, consulte las secciones Sobre desbordamiento y división
aritméticos por cero y Operadores comprobados definidos por el usuario del artículo
Operadores aritméticos .
C#
double a = double.MaxValue;
int b = unchecked((int)a);
try
b = checked((int)a);
catch (OverflowException e)
C#
int factor = 2;
try
checked
catch (OverflowException e)
Console.WriteLine(e.Message);
}
try
checked
catch (OverflowException e)
7 Nota
Vea también
Referencia de C#
Opción del compilador CheckForOverflowUnderflow
instrucciones activadas y desactivadas
(referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
C#
uint a = uint.MaxValue;
unchecked
try
checked
Console.WriteLine(a + 1);
catch (OverflowException e)
7 Nota
Para obtener más información, consulte las secciones Sobre desbordamiento y división
aritméticos por cero y Operadores comprobados definidos por el usuario del artículo
Operadores aritméticos .
C#
double a = double.MaxValue;
int b = unchecked((int)a);
try
b = checked((int)a);
catch (OverflowException e)
C#
int factor = 2;
try
checked
catch (OverflowException e)
Console.WriteLine(e.Message);
}
try
checked
catch (OverflowException e)
7 Nota
Vea también
Referencia de C#
Opción del compilador CheckForOverflowUnderflow
instrucciones activadas y desactivadas
(referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
C#
uint a = uint.MaxValue;
unchecked
try
checked
Console.WriteLine(a + 1);
catch (OverflowException e)
7 Nota
Para obtener más información, consulte las secciones Sobre desbordamiento y división
aritméticos por cero y Operadores comprobados definidos por el usuario del artículo
Operadores aritméticos .
C#
double a = double.MaxValue;
int b = unchecked((int)a);
try
b = checked((int)a);
catch (OverflowException e)
C#
int factor = 2;
try
checked
catch (OverflowException e)
Console.WriteLine(e.Message);
}
try
checked
catch (OverflowException e)
7 Nota
Vea también
Referencia de C#
Opción del compilador CheckForOverflowUnderflow
instrucción fija: acceso seguro a la
memoria subyacente a una variable
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
C#
unsafe
byte[] bytes = { 1, 2, 3 };
7 Nota
Con una dirección de una variable. Use el operador address-of&, como se muestra
en el ejemplo siguiente:
C#
unsafe
Los campos de objeto son otro ejemplo de variables que se pueden anclar.
C#
unsafe
Console.Write(p[i]);
// output: 203040
C#
unsafe
Console.WriteLine(*p); // output: H
También puede usar la fixed palabra clave para declarar un búfer de tamaño fijo.
fixed (instrucción)
Variables fijas y móviles
Vea también
Referencia de C#
Código no seguro, tipos de puntero y punteros de función
Operadores relacionados con el puntero
unsafe
Parámetros de métodos (Referencia de
C#)
Artículo • 18/02/2023 • Tiempo de lectura: 10 minutos
En C#, los argumentos se pueden pasar a parámetros por valor o por referencia.
Recuerde que los tipos de C# pueden ser tipos de referencia ( class ) o tipos de valor
( struct ):
Como un struct es un tipo de valor, cuando pasa un struct mediante valor a un método,
el método recibe y funciona en una copia del argumento struct. El método no tiene
acceso al struct original en el método de llamada y, por lo tanto, no puede cambiarlo de
ninguna manera. El método solo puede cambiar la copia.
class TheClass
struct TheStruct
class TestClassAndStruct
c.willIChange = "Changed";
s.willIChange = "Changed";
ClassTaker(testClass);
StructTaker(testStruct);
Console.ReadKey();
/* Output:
*/
Si el método asigna el parámetro para hacer referencia a otro objeto, esos cambios
no son visibles desde el autor de la llamada.
Si el método modifica el estado del objeto al que hace referencia el parámetro,
esos cambios no son visibles desde el autor de la llamada.
C#
int n = 5;
System.Console.ReadKey();
x *= x;
/* Output:
*/
Si el método asigna el parámetro para hacer referencia a otro objeto, esos cambios
no son visibles desde el autor de la llamada.
Si el método modifica el estado del objeto al que hace referencia el parámetro,
esos cambios son visibles desde el autor de la llamada.
El ejemplo siguiente es el mismo que el anterior, salvo que el argumento se pasa como
un parámetro ref . El valor del argumento subyacente, n , se cambia cuando se modifica
x en el método.
C#
int n = 5;
System.Console.ReadKey();
x *= x;
/* Output:
*/
Si el método asigna el parámetro para hacer referencia a otro objeto, esos cambios
no son visibles desde el autor de la llamada.
Si el método modifica el estado del objeto al que hace referencia el parámetro,
esos cambios son visibles desde el autor de la llamada.
C#
int[] arr = { 1, 4, 5 };
Change(arr);
pArray = new int[5] { -3, -1, -2, -3, -4 }; // This change is local.
/* Output:
Inside Main, before calling the method, the first element is: 1
Inside Main, after calling the method, the first element is: 888
*/
El ejemplo siguiente es el mismo que el anterior, salvo que la palabra clave ref se
agrega a la llamada y al encabezado de método. Los cambios que tengan lugar en el
método afectan a la variable original en el programa que realiza la llamada.
C#
int[] arr = { 1, 4, 5 };
Change(ref arr);
pArray[0] = 888;
/* Output:
Inside Main, before calling the method, the first element is: 1
Inside Main, after calling the method, the first element is: -3
*/
Todos los cambios que tienen lugar dentro del método afectan a la matriz original en
Main . De hecho, la matriz original se reasigna mediante el operador new . Por tanto,
Modificadores
Los parámetros declarados para un método sin in, ref o out se pasan al método llamado
por valor. Los modificadores ref , in y out difieren en las reglas de asignación:
Esta sección describe las palabras clave que puede usar para declarar parámetros de
métodos:
Vea también
Referencia de C#
Palabras clave de C#
Listas de argumentos en la especificación del lenguaje C#. La especificación del
lenguaje es la fuente definitiva de la sintaxis y el uso de C#.
params (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Una lista separada por comas de argumentos del tipo de los elementos de la
matriz.
Una matriz de argumentos del tipo especificado.
Sin argumentos. Si no envía ningún argumento, la longitud de la lista params es
cero.
Ejemplo
En el ejemplo siguiente se muestran varias maneras de enviar argumentos a un
parámetro params .
C#
Console.WriteLine();
Console.WriteLine();
// specified type.
UseParams(1, 2, 3, 4);
UseParams2();
int[] myIntArray = { 5, 6, 7, 8, 9 };
UseParams(myIntArray);
UseParams2(myObjArray);
//UseParams(myObjArray);
// The following call does not cause an error, but the entire
UseParams2(myIntArray);
/*
Output:
1 2 3 4
1 a test
5 6 7 8 9
2 b test again
System.Int32[]
*/
Consulte también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Parámetros de métodos
Modificador del parámetro in
(referencia de C#)
Artículo • 05/10/2022 • Tiempo de lectura: 5 minutos
La palabra clave in hace que los argumentos se pasen por referencia pero garantiza
que el argumento no se modifica. Hace que el parámetro formal sea un alias para el
argumento, que debe ser una variable. En otras palabras, cualquier operación en el
parámetro se realiza en el argumento. Es como las palabras clave ref o out, salvo que el
método al que se llama no puede modificar los argumentos in . Mientras que los
argumentos ref se pueden modificar, el método llamado debe modificar los
argumentos out y esas modificaciones se pueden observar en el contexto de la llamada.
C#
InArgExample(readonlyArgument);
//number = 19;
7 Nota
Además, la palabra clave in puede usarse con un parámetro de tipo genérico para
especificar que el parámetro de tipo es contravariante, parte de una instrucción
foreach o de una cláusula join de una consulta de LINQ. Para más información
sobre el uso de la palabra clave in en esos contextos, vea in, que además incluye
vínculos a todos estos usos.
Las variables que se han pasado como argumentos in deben inicializarse antes de
pasarse en una llamada de método. Sin embargo, es posible que el método llamado no
asigne ningún valor o modifique el argumento.
C#
class CS0663_Example
C#
class InOverloads
Una variable temporal permite argumentos en los que hay una conversión
implícita desde el tipo de argumento hacia el tipo de parámetro.
En todas las instancias anteriores, el compilador crea una variable temporal que
almacena el valor de la constante, la propiedad u otra expresión.
C#
// implementation removed
short s = 0;
int i = 42;
Supongamos ahora que hay disponible otro método que usa argumentos por valor. Los
resultados cambian como se muestra en este código:
C#
// implementation removed
// implementation removed
short s = 0;
int i = 42;
7 Nota
El código anterior usa int como el tipo de argumento para simplificar el trabajo.
Como int no es más grande que una referencia en la mayoría de máquinas
modernas, no supone ninguna ventaja pasar un único int como una referencia de
solo lectura.
Puede obtener más información sobre el modificador in y sobre cómo difiere de ref y
out en el artículo sobre Escritura de código eficaz y seguro.
Especificación del lenguaje C#
Para obtener más información, consulte la Especificación del lenguaje C#. La
especificación del lenguaje es la fuente definitiva de la sintaxis y el uso de C#.
ref (Referencia de C#)
Artículo • 18/02/2023 • Tiempo de lectura: 10 minutos
La palabra clave ref indica que una variable es una referencia o un alias de otro
proyecto. Se usa en cinco contextos diferentes:
En una firma del método y en una llamada al método, para pasar un argumento a
un método mediante referencia. Para más información, vea Pasar un argumento
mediante referencia.
En una firma del método, para devolver un valor al autor de la llamada mediante
referencia. Para obtener más información, consulte Valores devueltos de referencia.
En un cuerpo de miembro, para indicar que un valor devuelto de referencia se
almacena localmente como una referencia que el autor de la llamada pretende
modificar. O para indicar que una variable local tiene acceso a otro valor por
referencia. Para más información, vea Variables locales de tipo ref.
En una declaración struct , para declarar ref struct o readonly ref struct . Para
obtener más información, vea el artículo ref struct.
En una declaración ref struct , para declarar que un campo es una referencia.
Consulte el artículo sobre el ref campo.
Por ejemplo, supongamos que el autor de la llamada pasa una expresión de variable
local o una expresión de acceso a un elemento de matriz. El método al que se llama
puede reemplazar el objeto al que hace referencia el parámetro ref. En ese caso, la
variable local del autor de la llamada o el elemento de matriz hace referencia al nuevo
objeto en la devolución del método.
7 Nota
C#
int number = 1;
Method(ref number);
Console.WriteLine(number);
// Output: 45
Los miembros de una clase no pueden tener signaturas que se diferencien solo por ref ,
in o out . Si la única diferencia entre dos miembros de un tipo es que uno de ellos tiene
C#
class CS0663_Example
el ejemplo siguiente.
C#
class RefOverloadExample
Las propiedades no son variables. Son métodos y no se pueden pasar a parámetros ref .
Las palabras clave ref , in y out no pueden usarse para estos tipos de métodos:
C#
class Product
ItemName = name;
ItemID = newID;
itemRef.ItemID = 12345;
item.ItemName, item.ItemID);
ChangeByReference(ref item);
item.ItemName, item.ItemID);
Para obtener más información sobre cómo pasar tipos de referencia por valor y por
referencia, vea Pasar parámetros Reference-Type .
C#
C#
Para que el autor de la llamada modifique el estado del objeto, el valor devuelto de
referencia debe almacenarse en una variable que se defina explícitamente como una
variable local de tipo ref.
Este es un ejemplo de valor devuelto de referencia más completo que muestra la firma y
el cuerpo del método.
C#
if (predicate(matrix[i, j]))
El método llamado también puede declarar el valor devuelto como ref readonly para
devolver el valor por referencia y exigir que el código de llamada no pueda modificar el
valor devuelto. El método de llamada puede evitar copiar el valor devuelto si lo
almacena en una variable de tipo ref readonly.
Puede definir un valor ref local mediante la palabra clave ref en dos lugares:
Por ejemplo, en la siguiente instrucción se define un valor de variable local de tipo ref
que se devuelve mediante un método denominado GetEstimatedValue :
C#
Puede acceder a un valor por referencia de la misma manera. En algunos casos, acceder
a un valor por referencia aumenta el rendimiento, ya que evita una operación de copia
potencialmente cara. Por ejemplo, en la instrucción siguiente se muestra cómo definir
una variable local de referencia que se usa para hacer referencia a un valor.
C#
En los dos ejemplos la palabra clave ref debe usarse en ambos lugares. De lo contrario,
el compilador genera el error CS8172: «No se puede inicializar una variable por
referencia con un valor».
La variable de iteración de la instrucción foreach puede ser una variable local ref o una
variable local ref readonly. Para más información, vea el artículo sobre la instrucción
foreach. Puede reasignar una variable local de tipo ref readonly o local de tipo ref con el
operador de asignación ref.
se puede modificar.
Un ejemplo de valores devueltos y variables
locales de tipo ref
En el ejemplo siguiente se define una clase Book que tiene dos campos String, Title y
Author . También define una clase BookCollection que incluye una matriz privada de
objetos Book . Los objetos book individuales se devuelven mediante referencia llamando
a su método GetBookByTitle .
C#
private Book[] books = { new Book { Title = "Call of the Wild, The",
Author = "Jack London" },
};
if (title == books[ctr].Title)
Console.WriteLine($"{book.Title}, by {book.Author}");
Console.WriteLine();
C#
bc.ListBooks();
if (book != null)
bc.ListBooks();
//
campos ref
En tipos ref struct puede declarar campos que son campos ref . Los campos ref solo
son válidos en tipos ref struct para garantizar que la referencia no sobrevive al objeto
al que se refiere. Esta característica habilita tipos como System.Span<T>:
C#
El tipo Span<T> almacena una referencia a través de la cual accede a los elementos
consecutivos. Una referencia habilita al objeto Span<T> para evitar realizar copias del
almacenamiento al que hace referencia.
La palabra clave out hace que los argumentos se pasen por referencia. Hace que el
parámetro formal sea un alias para el argumento, que debe ser una variable. En otras
palabras, cualquier operación en el parámetro se realiza en el argumento. Esto es como
la palabra clave ref, salvo que ref requiere que se inicialice la variable antes de pasarla.
También es como la palabra clave in, salvo que in no permite que el método llamado
modifique el valor del argumento. Para usar un parámetro out , tanto la definición de
método como el método de llamada deben utilizar explícitamente la palabra clave out .
Por ejemplo:
C#
int initializeInMethod;
OutArgExample(out initializeInMethod);
number = 44;
7 Nota
La palabra clave out también puede usarse con un parámetro de tipo genérico
para especificar que el parámetro de tipo es covariante. Para obtener más
información sobre el uso de la palabra clave out en este contexto, vea Out
(Modificador genérico).
Las variables que se han pasado como argumentos out no tienen que inicializarse antes
de pasarse en una llamada al método. En cambio, se necesita el método que se ha
llamado para asignar un valor antes de que el método se devuelva.
Las palabras clave in , ref y out no se consideran parte de la firma del método con el
fin de resolver la sobrecarga. Por lo tanto, los métodos no pueden sobrecargarse si la
única diferencia es que un método toma un argumento ref o in y el otro toma un
argumento out . Por ejemplo, el código siguiente, no se compilará:
C#
class CS0663_Example
C#
class OutOverloadExample
Las propiedades no son variables y, por tanto, no pueden pasarse como parámetros
out .
Las palabras clave in , ref y out no pueden usarse para estos tipos de métodos:
Métodos de iterador, que incluyen una instrucción yield return o yield break .
C#
void Method(out int answer, out string message, out string stillNull)
answer = 44;
stillNull = null;
int argNumber;
Console.WriteLine(argNumber);
Console.WriteLine(argMessage);
Console.WriteLine(argDefault == null);
// 44
// True
C#
int number;
else
C#
else
En el ejemplo anterior, la variable number está fuertemente tipada como int . También
puede declarar una variable local con tipo implícito como se muestra en el siguiente
ejemplo.
C#
else
Vea también
Declaración de variables insertadas (regla de estilo IDE0018)
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Parámetros de métodos
namespace
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
La palabra clave namespace se usa para declarar un ámbito que contiene un conjunto de
objetos relacionados. Puede usar un espacio de nombres para organizar los elementos
de código y crear tipos únicos globales.
C#
namespace SampleNamespace
class SampleClass { }
interface ISampleInterface { }
struct SampleStruct { }
enum SampleEnum { a, b }
namespace Nested
class SampleClass2 { }
Las declaraciones de espacio de nombres con ámbito de archivo permiten declarar que
todos los tipos de un archivo están en un único espacio de nombres. Las declaraciones
de espacio de nombres con ámbito de archivo están disponibles con C# 10. El ejemplo
siguiente es similar al anterior, pero usa una declaración de espacio de nombres con
ámbito de archivo:
C#
using System;
namespace SampleFileScopedNamespace;
class SampleClass { }
interface ISampleInterface { }
struct SampleStruct { }
enum SampleEnum { a, b }
C#
namespace SampleNamespace;
class AnotherSampleClass
System.Console.WriteLine(
"SampleMethod inside SampleNamespace");
// declarations...
class
interface
struct
enum
delegate
los espacios de nombres anidados se pueden declarar excepto en declaraciones de
espacio de nombres con ámbito de archivo
Los espacios de nombres tienen acceso público implícitamente. Para ver una descripción
de los modificadores de acceso que se pueden asignar a los elementos de un espacio
de nombres, vea Modificadores de acceso.
Es posible definir un espacio de nombres en dos o más declaraciones. Por ejemplo, en el
ejemplo siguiente se definen dos clases como parte del espacio de nombres MyCompany :
C#
namespace MyCompany.Proj1
class MyClass
namespace MyCompany.Proj1
class MyClass1
C#
namespace SomeNameSpace
Nested.NestedNameSpaceClass.SayHello();
// a nested namespace
namespace Nested
Console.WriteLine("Hello");
// Output: Hello
Vea también
Preferencias de declaración de espacio de nombres (IDE0160 e IDE0161)
Referencia de C#
Palabras clave de C#
using
using static
Calificadores de alias de espacio de nombres::
Espacios de nombres
using (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Consulte también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Espacios de nombres
extern
using (directiva)
Artículo • 15/02/2023 • Tiempo de lectura: 10 minutos
La directiva using permite usar tipos definidos en un espacio de nombres sin especificar
el espacio de nombres completo de ese tipo. En su forma básica, la directiva using
importa todos los tipos de un único espacio de nombres, como se muestra en el
ejemplo siguiente:
C#
using System.Text;
El modificador global tiene el mismo efecto que agregar la misma directiva using
a todos los archivos de código fuente del proyecto. Este modificador se presentó
en C# 10.
El modificador static importa los miembros static y los tipos anidados de un
solo tipo en lugar de importar todos los tipos de un espacio de nombres.
Puede combinar los dos modificadores para importar los miembros estáticos de un tipo
en todos los archivos de código fuente del proyecto.
También puede crear un alias para un espacio de nombres o un tipo con una directiva de
alias using.
C#
7 Nota
La palabra clave using también se usa para crear instrucciones using, que ayudan a
garantizar que los objetos IDisposable, como archivos y fuentes, se tratan
correctamente. Para obtener más información sobre la instrucción using, consulte
Instrucción using.
Cree una directiva using para usar los tipos de un espacio de nombres sin tener que
especificarlo. Una directiva using no proporciona acceso a los espacios de nombres que
están anidados en el espacio de nombres especificado. Los espacios de nombres se
dividen en dos categorías: definidos por el sistema y definidos por el usuario. Los
espacios de nombres definidos por el usuario son espacios de nombres definidos en el
código. Para obtener una lista de los espacios de nombres definidos por el sistema, vea
Explorador de API de .NET.
modificador global
Agregar el modificador global a una directiva using hará que using se aplique a todos
los archivos de la compilación (normalmente un proyecto). La directiva global using se
agregó en C# 10. La sintaxis es:
C#
Una directiva global using puede aparecer al principio de cualquier archivo de código
fuente. Todas las directivas global using de un solo archivo deben aparecer antes de:
C#
) Importante
Las plantillas de C# para .NET 6 usan instrucciones de nivel superior. Es posible que
la aplicación no coincida con el código de este artículo si ya ha actualizado a
.NET 6. Para obtener más información, consulte el artículo Las nuevas plantillas de
C# generan instrucciones de nivel superior.
Microsoft.NET.Sdk
Microsoft.NET.Sdk.Web
Microsoft.NET.Sdk.Worker
Estas directivas de global using implícitas incluyen los espacios de nombres más
comunes para el tipo de proyecto.
static (modificador)
La directiva using static designa un tipo a cuyos miembros estáticos y tipos anidados
se puede acceder sin especificar un nombre de tipo. La sintaxis es:
C#
La directiva using static se aplica a cualquier tipo que tenga miembros estáticos (o
tipos anidados), aunque también tenga miembros de instancia. Pero los miembros de
instancia solo pueden invocarse a través de la instancia del tipo.
Puede acceder a los miembros estáticos de un tipo sin tener que calificar el acceso con
el nombre del tipo:
C#
class Program
WriteLine(Sqrt(3*3 + 4*4));
C#
using System;
Radius = radius;
Al eliminar la necesidad de hacer referencia explícitamente a la clase Math cada vez que
se hace referencia a un miembro, la directiva using static genera un código más
limpio:
C#
using System;
Radius = radius;
using static importa solo los miembros estáticos accesibles y los tipos anidados
using static habilita los métodos de extensión declarados en el tipo especificado estén
para la búsqueda de métodos de extensión. Sin embargo, los nombres de los métodos
de extensión no se importan en el ámbito de referencia sin calificar del código.
Los métodos con el mismo nombre que se importen desde tipos distintos con distintas
directivas using static en la misma unidad de compilación o espacio de nombres
forman un grupo de métodos. La resolución de sobrecarga dentro de estos grupos de
método sigue reglas normales de C#.
En el ejemplo siguiente se usa la directiva using static para que los miembros
estáticos de las clases Console, Math y String estén disponibles sin tener que especificar
su nombre de tipo.
C#
using System;
class Program
WriteLine(s);
else {
WriteLine("Invalid input...");
Radius = radius;
//
// Radius: 12.45
// Diameter: 24.90
// Circumference: 78.23
// Area: 486.95
En el ejemplo, también podría haberse aplicado la directiva using static al tipo Double.
Agregar esa directiva habría permitido llamar al método TryParse(String, Double) sin
especificar un nombre de tipo. Sin embargo, el uso de TryParse sin un nombre de tipo
crea un código menos legible, ya que es necesario comprobar las directivas using
static para determinar el método TryParse del tipo numérico al que se llama.
using static también se aplica a los tipos enum . Al agregar using static con la
enumeración, el tipo ya no es necesario para usar los miembros de la enumeración.
C#
enum Color
Red,
Green,
Blue
class Program
alias using
Cree una directiva de alias using para facilitar la calificación de un identificador como
espacio de nombres o tipo. En cualquier directiva using , hay que usar el espacio de
nombres o el tipo con cualificación completa, independientemente de las directivas
using que los precedan. No se puede usar ningún alias using en la declaración de una
directiva using . Por ejemplo, en el ejemplo siguiente se genera un error del compilador:
C#
using s = System.Text;
En el ejemplo siguiente se muestra cómo definir y usar un alias using para un espacio
de nombres:
C#
namespace PC
class A
void M()
namespace MyCompany
{
namespace Project
Una directiva de alias using no puede tener un tipo genérico abierto en el lado derecho.
Por ejemplo, no puede crear un alias using para un elemento List<T> , pero puede crear
uno para un elemento List<int> .
En el ejemplo siguiente se muestra cómo definir una directiva using y un alias using
para una clase:
C#
using System;
namespace NameSpace1
namespace NameSpace2
class MyClass<T>
namespace NameSpace3
class MainClass
Console.WriteLine(instance1);
Console.WriteLine(instance2);
// Output:
Para obtener más información sobre el uso del espacio de nombres MyServices de
Visual Basic, consulte Desarrollo con My.
C#
// Duplicate a directory
Microsoft.VisualBasic.FileIO.FileSystem.CopyDirectory(
@"C:\original_directory",
@"C:\copy_of_original_directory");
Vea también
Regla de estilo IDE0005: Eliminación de directivas "using" innecesarias
Regla de estilo IDE0065: Colocación de directivas "using"
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Espacios de nombres
using (instrucción)
using (Instrucción, Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 4 minutos
La instrucción using ofrece una sintaxis adecuada que garantiza el uso correcto de
objetos IDisposable. La instrucción await using garantiza el uso correcto de los objetos
IAsyncDisposable. El lenguaje admite tipos descartables asincrónicos que implementan
la interfaz System.IAsyncDisposable.
Ejemplo
En el ejemplo siguiente se muestra cómo usar la instrucción using .
C#
string? item;
do
item = reader.ReadLine();
Console.WriteLine(item);
C#
string? item;
do
item = reader.ReadLine();
Console.WriteLine(item);
Comentarios
File y Font son ejemplos de tipos administrados que acceden a recursos no
administrados (en este caso, identificadores de archivo y contextos de dispositivo). Hay
muchos otros tipos de recursos no administrados y tipos de la biblioteca de clases que
los encapsulan. Todos estos tipos deben implementar la interfaz IDisposable o la
interfaz IAsyncDisposable.
C#
try
string? item;
do
item = reader.ReadLine();
Console.WriteLine(item);
finally
reader?.Dispose();
Se pueden declarar varias instancias de un tipo en una sola instrucción using , tal y
como se muestra en el ejemplo siguiente. Tenga en cuenta que no se pueden usar
variables con tipo implícito ( var ) cuando se declaran varias variables en una sola
instrucción:
C#
Two
Three
Four.";
D.";
string? item;
do
item = left.ReadLine();
Console.Write(item);
Console.Write(" ");
item = right.ReadLine();
Console.WriteLine(item);
También puede combinar varias declaraciones del mismo tipo con la nueva sintaxis de
declaración, tal y como se muestra en el siguiente ejemplo:
C#
Two
Three
Four.";
D.";
string? item;
do
item = left.ReadLine();
Console.Write(item);
Console.Write(" ");
item = right.ReadLine();
Console.WriteLine(item);
Puede crear una instancia del objeto de recurso y luego pasar la variable a la
instrucción using , pero esto no es un procedimiento recomendado. En este caso,
después de que el control abandone el bloque using el objeto permanece en el ámbito,
pero probablemente ya no tenga acceso a sus recursos no administrados. En otras
palabras, ya no se inicializa totalmente. Si intenta usar el objeto fuera del bloque using ,
corre el riesgo de iniciar una excepción. Por este motivo, es mejor crear una instancia del
objeto en la instrucción using y limitar su ámbito al bloque using .
C#
using (reader)
string? item;
do
item = reader.ReadLine();
Console.WriteLine(item);
Para obtener más información sobre cómo eliminar objetos IDisposable , vea Uso de
objetos que implementan IDisposable.
Vea también
Uso de la instrucción "using" simple (regla de estilo IDE0063)
Referencia de C#
Guía de programación de C#
Palabras clave de C#
using (directiva)
Recolección de elementos no utilizados
Uso de objetos que implementan IDisposable
IDisposable interface (Interfaz IDisposable)
Instrucción using en C# 8.0
Artículo Implementación de un método DisposeAsync
alias externo (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Es posible que deba hacer referencia a dos versiones de ensamblados que tienen los
mismos nombres de tipo completos. Por ejemplo, es posible que tenga que usar dos o
más versiones de un ensamblado en la misma aplicación. Mediante el uso de un alias de
ensamblado externo, los espacios de nombres de cada ensamblado pueden ajustarse en
espacios de nombres de nivel de raíz denominados por el alias, lo que permite que se
usen en el mismo archivo.
7 Nota
Para hacer referencia a dos ensamblados con los mismos nombres de tipo completos,
debe especificarse un alias en un símbolo del sistema, como sigue:
/r:GridV1=grid.dll
/r:GridV2=grid20.dll
Esto crea los alias externos GridV1 y GridV2 . Para usar estos alias desde dentro de un
programa, se hace referencia a ellos mediante la palabra clave extern . Por ejemplo:
C#
Ahora puede crear un alias para un espacio de nombres o un tipo mediante la directiva
de alias. Para más información, consulte la directiva using.
C#
Consulte también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
:: !
References (opciones del compilador de C#)
Restricción new (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Aplique la restricción new a un tipo de parámetro cuando una clase genérica cree otras
instancias del tipo, tal y como se muestra en el ejemplo siguiente:
C#
public T GetNewItem()
Cuando use la restricción new() con otras restricciones, se debe especificar en último
lugar:
C#
{ }
También puede usar la palabra clave new para crear una instancia de un tipo o como un
modificador de declaración de miembro.
Vea también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Genéricos
where (restricción de tipo genérico)
(Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 5 minutos
La cláusula where en una definición genérica especifica restricciones en los tipos que se
usan como argumentos para los parámetros de tipo en un tipo genérico, método,
delegado o función local. Las restricciones pueden especificar interfaces o clases base, o
bien requerir que un tipo genérico sea una referencia, un valor o un tipo no
administrado. Declaran las funcionalidades que debe tener el argumento de tipo, y
deben colocarse después de cualquier clase base declarada o interfaces implementadas.
Por ejemplo, se puede declarar una clase genérica, AGenericClass , de modo que el
parámetro de tipo T implemente la interfaz IComparable<T>:
C#
7 Nota
La cláusula where también puede incluir una restricción de clase base. La restricción de
clase base indica que un tipo que se va a usar como argumento de tipo para ese tipo
genérico tiene la clase especificada como clase base, o bien es la clase base. Si se usa la
restricción de clase base, debe aparecer antes que cualquier otra restricción de ese
parámetro de tipo. Algunos tipos no están permitidos como restricción de clase base:
Object, Array y ValueType. En el ejemplo siguiente se muestran los tipos que ahora se
pueden especificar como clase base:
C#
En un contexto que admite un valor NULL, se aplica la nulabilidad del tipo de clase base.
Si la clase base no acepta valores NULL (por ejemplo, Base ), el argumento de tipo no
debe aceptar valores NULL. Si la clase base admite un valor NULL (por ejemplo, Base? ),
el argumento de tipo puede ser un tipo de referencia que acepte o no valores NULL. El
compilador emite una advertencia si el argumento de tipo es un tipo de referencia que
admite un valor NULL cuando la clase base no acepta valores NULL.
La cláusula where puede especificar que el tipo es class o struct . La restricción struct
elimina la necesidad de especificar una restricción de clase base de System.ValueType . El
tipo System.ValueType no se puede usar como restricción de clase base. En el ejemplo
siguiente se muestran las restricciones class y struct :
C#
where T : class
where U : struct
{ }
En un contexto que admite un valor NULL, la restricción class requiere que un tipo sea
un tipo de referencia que no acepta valores NULL. Para permitir tipos de referencia que
admitan un valor NULL, use la restricción class? , que permite tipos de referencia que
aceptan y que no aceptan valores NULL.
C#
public abstract class B
La restricción default se usa para especificar que la clase derivada invalida el método
sin la restricción en la clase derivada o la implementación de interfaz explícita. Solo es
válido en métodos que invalidan métodos base o implementaciones de interfaz
explícitas:
C#
public class D : B
) Importante
C#
#nullable enable
class NotNullContainer<T>
where T : notnull
#nullable restore
administrados. La restricción unmanaged hace que sea más fácil escribir código de
interoperabilidad de bajo nivel en C#. Esta restricción habilita las rutinas reutilizables en
todos los tipos no administrados. La restricción unmanaged no se puede combinar con
las restricciones class o struct . La restricción unmanaged exige que el tipo sea struct :
C#
class UnManagedWrapper<T>
where T : unmanaged
{ }
La cláusula where también podría incluir una restricción de constructor, new() . Esta
restricción hace posible crear una instancia de un parámetro de tipo con el operador
new . La restricción new() permite que el compilador sepa que cualquier argumento de
tipo especificado debe tener accesible un constructor sin parámetros. Por ejemplo:
C#
Con varios parámetros de tipo, use una cláusula where para cada parámetro de tipo, por
ejemplo:
C#
namespace CodeExample
C#
public void MyMethod<T>(T t) where T : IMyInterface { }
C#
Para obtener información sobre los delegados genéricos, vea Delegados genéricos.
Consulte también
Referencia de C#
Guía de programación de C#
Introducción a los genéricos
new (restricción)
Restricciones de tipos de parámetros
base (Referencia de C#)
Artículo • 17/02/2023 • Tiempo de lectura: 2 minutos
La palabra clave base se usa para acceder a los miembros de la clase base desde una
clase derivada. Úselo si desea:
Llamar a un método en la clase base que haya sido reemplazado por otro método.
El uso de la base palabra clave desde dentro de un método estático dará un error.
Ejemplo 1
En este ejemplo, la clase base Person y la clase derivada Employee tienen un método
denominado Getinfo . Mediante el uso de la palabra clave base , es posible llamar al
método Getinfo de la clase base desde la clase derivada.
C#
base.GetInfo();
class TestClass
E.GetInfo();
/*
Output
SSN: 444-55-6666
*/
Ejemplo 2
En este ejemplo se muestra cómo especificar el constructor de clase base al que se
llama al crear instancias de una clase derivada.
C#
int num;
public BaseClass()
Console.WriteLine("in BaseClass()");
public BaseClass(int i)
num = i;
return num;
/*
Output:
in BaseClass()
in BaseClass(int i)
*/
Consulte también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
this
this (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
La palabra clave this hace referencia a la instancia actual de la clase y también se usa
como modificador del primer parámetro de un método de extensión.
7 Nota
En este artículo se describe el uso de this con instancias de clase. Para obtener
más información sobre su uso en métodos de extensión, vea Métodos de
extensión.
C#
this.name = name;
this.alias = alias;
C#
CalcTax(this);
C#
Las funciones miembro estáticas no tienen un puntero this , debido a que existen en el
nivel de clase y no como parte de un objeto. Es un error hacer referencia a this en un
método estático.
Ejemplo
En este ejemplo, se usa this para calificar los miembros de la clase Employee , name y
alias , que están ocultos por nombres similares. También se usa para pasar un objeto al
método CalcTax , que pertenece a otra clase.
C#
class Employee
// Constructor:
this.name = name;
this.alias = alias;
// Printing method:
class Tax
class MainClass
// Create objects:
// Display results:
E1.printEmployee();
/*
Output:
Alias: mpan
Taxes: $240.00
*/
Vea también
this Preferencias de estilo de código (IDE0003 e IDE0009)
Referencia de C#
Guía de programación de C#
Palabras clave de C#
base
Métodos
null (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
La palabra clave null es un literal que representa una referencia nula que no hace
referencia a ningún objeto. null es el valor predeterminado de las variables de tipo de
referencia. Los tipos de valor normales no pueden ser NULL, excepto los tipos de valor
que admiten un valor NULL.
C#
class Program
class MyClass
MyClass mc;
mc = null;
// a run-time NullReferenceException.
// mc.MyMethod();
mc = new MyClass();
mc.MyMethod();
mc = null;
string s = null;
bool b = (t.Equals(s));
Console.WriteLine(b);
// operand is null.
// Returns true.
int? i = null;
System.Console.ReadKey();
}
Vea también
Referencia de C#
Palabras clave de C#
Valores predeterminados de los tipos de C#
Nothing (Visual Basic)
bool (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Para realizar operaciones lógicas con valores del tipo bool , use operadores lógicos
booleanos. El tipo bool es el tipo de resultado de los operadores de comparación e
igualdad. Una expresión bool puede ser una expresión condicional de control en las
instrucciones if, do, while y for, así como en el operador condicional ?:.
Literales
Puede usar los literales true y false para inicializar una variable bool o para pasar un
valor bool :
C#
Para más información sobre los tipos de valor que admiten un valor NULL, consulte
Tipos de valor que admiten un valor NULL.
Conversiones
C# solo proporciona dos conversiones que implican al tipo bool . Son una conversión
implícita al tipo bool? que acepta valores NULL correspondiente y una conversión
explícita del tipo bool? . Sin embargo, .NET proporciona métodos adicionales que se
pueden usar para realizar una conversión al tipo bool , o bien revertirla. Para obtener
más información, vea la sección Convertir a y desde valores booleanos de la página de
referencia de la API System.Boolean.
Vea también
Referencia de C#
Tipos de valor
operadores true y false
default (referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Vea también
Referencia de C#
Palabras clave de C#
add (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
La palabra clave contextual add se usa para definir un descriptor de acceso de eventos
personalizado que se invoca cuando el código de cliente se suscribe a su evento. Si
proporciona un descriptor de acceso add personalizado, también debe proporcionar un
descriptor de acceso remove.
Ejemplo
En el ejemplo siguiente se muestra un evento que tiene descriptores de acceso add y
remove personalizados. Para obtener el ejemplo completo, consulte Procedimiento
Implementar eventos de interfaz.
C#
Vea también
Eventos
get (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
En el ejemplo siguiente se definen unos descriptores de acceso get y set para una
propiedad denominada Seconds . Usa un campo privado denominado _seconds para
respaldar el valor de la propiedad.
C#
class TimePeriod
A menudo, el descriptor de acceso get consta de una única instrucción que devuelve un
valor, como en el ejemplo anterior. Puede implementar el descriptor de acceso get
como un miembro con forma de expresión. En el ejemplo siguiente se implementan los
descriptores de acceso get y set como miembros con forma de expresión.
C#
class TimePeriod
En los casos sencillos en los que los descriptores de acceso get y set de una propiedad
no realizan ninguna operación aparte de establecer o recuperar un valor en un campo
de respaldo privado, puede aprovechar la compatibilidad del compilador de C# con las
propiedades implementadas automáticamente. En el ejemplo siguiente se implementa
Hours como una propiedad implementada automáticamente.
C#
class TimePeriod2
Consulte también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Propiedades
init (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
En el ejemplo siguiente se definen los descriptores de acceso get y init para una
propiedad denominada YearOfBirth . Usa un campo privado denominado _yearOfBirth
para respaldar el valor de la propiedad.
C#
class Person_InitExample
A menudo, el descriptor de acceso init consta de una única instrucción que asigna un
valor, como en el ejemplo anterior. Tenga en cuenta que, debido a init , lo siguiente no
funcionará:
C#
YearOfBirth = 1984
};
john.YearOfBirth = 1926; //Not allowed, as its value can only be set once in
the constructor
El descriptor de acceso init se puede usar como miembro con forma de expresión.
Ejemplo:
C#
class Person_InitExampleExpressionBodied
C#
class Person_InitExampleAutoProperty
Consulte también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Propiedades
Tipo parcial (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Las definiciones de tipo parcial permiten dividir la definición de una clase, estructura,
interfaz o registro en varios archivos.
En File1.cs:
C#
namespace PC
partial class A
int num = 0;
void MethodA() { }
La declaración en File2.cs:
C#
namespace PC
partial class A
void MethodB() { }
Observaciones
Dividir un tipo de clase, estructura o interfaz en varios archivos puede resultar útil
cuando trabaja con proyectos de gran tamaño o con código generado
automáticamente, como el proporcionado por el Diseñador de Windows Forms. Un tipo
parcial puede contener un método parcial. Para más información, vea Clases y métodos
parciales.
Consulte también
Referencia de C#
Guía de programación de C#
Modificadores
Introducción a los genéricos
partial (Método) (Referencia de C#)
Artículo • 10/03/2023 • Tiempo de lectura: 2 minutos
Devuelve void.
Cualquier método que no cumpla todas estas restricciones (por ejemplo, public virtual
partial void ) debe proporcionar una implementación.
C#
namespace PM
partial class A
partial class A
Los métodos parciales también pueden ser útiles en combinación con los generadores
de código fuente. Por ejemplo, se podría definir una expresión regular con el siguiente
patrón:
C#
[GeneratedRegex("(dog|cat|fish)")]
Vea también
Referencia de C#
partial (tipo)
remove (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Ejemplo
En el ejemplo siguiente, se muestra un evento con descriptores de acceso add y remove
personalizados. Para obtener el ejemplo completo, consulte Procedimiento Implementar
eventos de interfaz.
C#
Vea también
Eventos
set (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
En el ejemplo siguiente se definen unos descriptores de acceso get y set para una
propiedad denominada Seconds . Usa un campo privado denominado _seconds para
respaldar el valor de la propiedad.
C#
class TimePeriod
A menudo, el descriptor de acceso set consta de una única instrucción que asigna un
valor, como en el ejemplo anterior. Puede implementar el descriptor de acceso set
como un miembro con forma de expresión. En el ejemplo siguiente se implementan los
descriptores de acceso get y set como miembros con forma de expresión.
C#
class TimePeriod
En los casos sencillos en los que los descriptores de acceso get y set de una propiedad
no realizan ninguna operación aparte de establecer o recuperar un valor en un campo
de respaldo privado, puede aprovechar la compatibilidad del compilador de C# con las
propiedades implementadas automáticamente. En el ejemplo siguiente se implementa
Hours como una propiedad implementada automáticamente.
C#
class TimePeriod2
Consulte también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
Propiedades
when (referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
La palabra clave contextual when se usa para especificar una condición de filtro en los
siguientes contextos:
C#
donde expr es una expresión que se evalúa como un valor booleano. Si devuelve true ,
el controlador de excepciones se ejecuta; si devuelve false , no se ejecuta.
C#
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
Console.WriteLine(MakeRequest().Result);
try
return responseText;
catch (HttpRequestException e)
return e.Message;
Vea también
try/catch (Instrucción)
try/catch/finally (Instrucción)
value (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
C#
class MyBaseClass
get
return _name;
set
if (!string.IsNullOrEmpty(value))
_name = value;
else
_name = "Unknown";
Consulte también
Referencia de C#
Guía de programación de C#
Palabras clave de C#
instrucción yield: proporcione el
siguiente elemento.
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
en el ejemplo siguiente:
C#
Console.Write(i);
Console.Write(" ");
// Output: 0 2 4 6 8
yield return i;
}
en el ejemplo siguiente:
C#
// Output: 2 3 4 5
// Output: 9 8 7
if (n > 0)
yield return n;
else
yield break;
C#
Console.Write(n);
Console.Write(" ");
// Output: 0 2 4 6 8
await Task.Delay(1000);
return 2 * seed;
Console.Write(coordinate);
Console.Write(" ");
// Output: 1 2 3
yield return X;
yield return Y;
yield return Z;
}
Ejecución de un iterador
La llamada de un iterador no la ejecuta inmediatamente, como se muestra en el ejemplo
siguiente:
C#
Console.WriteLine($"Caller: {i}");
Console.WriteLine("Iterator: start.");
yield return i;
Console.WriteLine($"Iterator: yielded {i}");
Console.WriteLine("Iterator: end.");
// Output:
// Iterator: start.
// Caller: 0
// Iterator: yielded 0
// Caller: 2
// Iterator: yielded 2
// Caller: 4
// Iterator: yielded 4
// Iterator: end.
Vea también
Referencia de C#
Iteradores
Procesar una iteración mediante colecciones en C#
foreach
await foreach
Palabras clave de consulta (Referencia
de C#)
Artículo • 22/09/2022 • Tiempo de lectura: 2 minutos
En esta sección, se incluyen las palabras clave contextuales que se usan en expresiones
de consulta.
En esta sección
Cláusula Descripción
from Especifica un origen de datos y una variable de rango (similar a una variable de
iteración).
where Filtra los elementos de origen en función de una o varias expresiones booleanas
separadas por operadores lógicos AND y OR ( && o || ).
select Especifica el tipo y la forma que tendrán los elementos de la secuencia devuelta
cuando se ejecute la consulta.
into Proporciona un identificador que puede actuar como una referencia a los
resultados de una combinación, un grupo o una cláusula select.
orderby Ordena los resultados de las consultas en orden ascendente o descendente según
el comparador predeterminado del tipo de elemento.
join Combina dos orígenes de datos en función de una comparación de igualdad entre
dos criterios de coincidencia especificados.
let Presenta una variable de rango para almacenar los resultados de la subexpresión en
una expresión de consulta.
Una expresión de consulta debe comenzar con una cláusula from , Además, una
expresión de consulta puede contener subconsultas, que también comienzan con una
cláusula from . La cláusula from especifica lo siguiente:
Tanto la variable de rango como el origen de datos están fuertemente tipados. El origen
de datos al que se hace referencia en la cláusula from debe tener un tipo de
IEnumerable, IEnumerable<T> o un tipo derivado como IQueryable<T>.
C#
class LowNums
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
// lowNums is an IEnumerable<int>
select num;
// Output: 4 1 3 2 0
Variable de rango
El compilador deduce el tipo de la variable de rango cuando el origen de datos
implementa IEnumerable<T>. Por ejemplo, si el origen tiene un tipo de
IEnumerable<Customer> , entonces se deduce que la variable de rango es Customer . La
única vez en que debe especificar el tipo explícitamente es cuando el origen es un tipo
IEnumerable no genérico como ArrayList. Para obtener más información, vea
En el ejemplo anterior, num se deduce que es de tipo int . Como la variable de rango
está fuertemente tipada, puede llamar a los métodos en ella o usarla en otras
operaciones. Por ejemplo, en lugar de escribir select num , podría escribir select
num.ToString() para hacer que la expresión de consulta devuelva una secuencia de
cadenas en lugar de enteros. O podría escribir select num + 10 para hacer que la
expresión devuelva la secuencia 14, 11, 13, 12, 10. Para obtener más información, vea
Cláusula select.
lista de resultados de las pruebas. Para tener acceso a la lista interna dentro de cada
elemento Student , puede usar las cláusulas from compuestas. La técnica es como usar
instrucciones foreach anidadas. Puede agregar cláusulas where u orderby a cualquier
cláusula from para filtrar los resultados. En el ejemplo siguiente se muestra una
secuencia de objetos Student , cada uno de los cuales contiene un List interno de
enteros que representa los resultados de las pruebas. Para tener acceso a la lista interna,
use una cláusula from compuesta. Puede insertar cláusulas entre las dos cláusulas from
si es necesario.
C#
class CompoundFrom
};
Console.WriteLine("scoreQuery:");
// anonymous type defined as new {string Last, int score}. That is,
Console.ReadKey();
/*
scoreQuery:
Omelchenko Score: 97
O'Donnell Score: 91
Mortensen Score: 94
Garcia Score: 97
Beebe Score: 91
*/
En el siguiente ejemplo se muestra cómo pueden usarse dos cláusulas from para formar
una combinación cruzada completa de dos orígenes de datos.
C#
class CompoundFrom2
var joinQuery1 =
var joinQuery2 =
Console.WriteLine("Cross join:");
Console.WriteLine("Filtered non-equijoin:");
Console.ReadKey();
/* Output:
Cross join:
A is matched to x
A is matched to y
A is matched to z
B is matched to x
B is matched to y
B is matched to z
C is matched to x
C is matched to y
C is matched to z
Filtered non-equijoin:
y is matched to A
y is matched to B
y is matched to C
z is matched to A
z is matched to B
z is matched to C
*/
Para obtener más información sobre las operaciones de combinación que usan varias
cláusulas from , vea Realizar operaciones de combinación externa izquierda.
Vea también
Palabras clave para consultas (LINQ)
Language-Integrated Query (LINQ)
where (Cláusula, Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 3 minutos
La cláusula where se usa en una expresión de consulta para especificar los elementos
del origen de datos que se devuelven en dicha expresión. Aplica una condición
booleana (predicate) a cada elemento de origen (al que hace referencia la variable de
rango) y devuelve aquellos en los que la condición especificada se cumple. Puede que
una sola expresión de consulta contenga varias cláusulas where y que una sola cláusula
contenga varias subexpresiones de predicado.
Ejemplo 1
En el ejemplo siguiente, la cláusula where filtra todos los números excepto los que son
inferiores a cinco. Si la cláusula where se quita, se devolverán todos los números del
origen de datos. La expresión num < 5 es el predicado que se aplica a cada elemento.
C#
class WhereSample
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var queryLowNums =
select num;
//Output: 4 1 3 2 0
Ejemplo 2
En una sola cláusula where , se pueden especificar todos los predicados que sean
necesarios mediante los operadores && y ||. En el ejemplo siguiente, la consulta
especifica dos predicados para seleccionar únicamente los números pares que sean
inferiores a cinco.
C#
class WhereSample2
// Data source.
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var queryLowNums2 =
select num;
Console.WriteLine();
var queryLowNums3 =
where num % 2 == 0
select num;
// Output:
// 4 2 0
// 4 2 0
Ejemplo 3
Puede que una cláusula where contenga uno o más métodos que devuelvan valores
booleanos. En el ejemplo siguiente, la cláusula where usa un método para determinar si
el valor actual de la variable de rango es par o impar.
C#
class WhereSample3
// Data source
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var queryEvenNums =
where IsEven(num)
select num;
return i % 2 == 0;
//Output: 4 8 6 2 0
Observaciones
La cláusula where es un mecanismo de filtrado. Se puede colocar prácticamente en
cualquier parte en una expresión de consulta, pero no puede ser la primera ni la última
cláusula. Puede que una cláusula where aparezca antes o después de una cláusula
group, en función de que haya que filtrar los elementos de origen antes o después de
agruparlos.
Vea también
Palabras clave para consultas (LINQ)
from (cláusula)
select (cláusula)
Filtrado de datos
LINQ en C#
Language-Integrated Query (LINQ)
select (Cláusula, Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 6 minutos
C#
class SelectSample1
IEnumerable<int> queryHighScores =
select score;
//Output: 97 92 81
C#
class SelectSample2
ContactInfo cInfo =
(from ci in app.contactList
where ci.ID == id
select ci)
.FirstOrDefault();
return cInfo;
};
};
select student;
Console.WriteLine(s.ToString());
IEnumerable<String> studentQuery2 =
select student.Last;
Console.WriteLine(s);
IEnumerable<ContactInfo> studentQuery3 =
Console.WriteLine(ci.ToString());
IEnumerable<int> studentQuery4 =
select student.Scores[0];
IEnumerable<double> studentQuery5 =
IEnumerable<double> studentQuery6 =
select student.Scores.Average();
IEnumerable<ScoreInfo> studentQuery8 =
Average = student.Scores.Average(),
ID = student.ID
};
IEnumerable<ContactInfo> studentQuery9 =
select ci;
Console.ReadKey();
/* Output
Claire O'Donnell:112
Sven Mortensen:113
Cesar Garcia:114
O'Donnell
Mortensen
Garcia
[email protected],206-555-0298
[email protected],206-555-1130
[email protected],206-555-0521
First score = 75
First score = 88
First score = 97
Average = 72.25
Average = 84.5
Average = 88.25
O'Donnell, Claire
Mortensen, Sven
Garcia, Cesar
*/
Vea también
Referencia de C#
Palabras clave para consultas (LINQ)
from (cláusula)
partial (Método) (Referencia de C#)
Tipos anónimos
LINQ en C#
Language-Integrated Query (LINQ)
group (Cláusula, Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 9 minutos
Puede finalizar una expresión de consulta con una cláusula group , como se muestra en
el ejemplo siguiente:
C#
var studentQuery1 =
C#
var studentQuery2 =
orderby g.Key
select g;
C#
Console.WriteLine(studentGroup.Key);
Tipos de clave
Las claves de grupo pueden ser de cualquier tipo, como una cadena, un tipo numérico
integrado, un tipo con nombre definido por el usuario o un tipo anónimo.
C#
// Same as previous example except we use the entire last name as a key.
var studentQuery3 =
C#
class GroupSample1
};
return students;
var booleanGroupQuery =
Console.ReadKey();
/* Output:
Low averages
Omelchenko, Svetlana:77.5
O'Donnell, Claire:72.25
Garcia, Cesar:75.5
High averages
Mortensen, Sven:93.5
Garcia, Debra:88.25
*/
C#
class GroupSample2
};
return students;
var studentQuery =
orderby g.Key
select g;
Console.ReadKey();
/* Output:
Omelchenko, Svetlana:77.5
O'Donnell, Claire:72.25
Garcia, Cesar:75.5
Garcia, Debra:88.25
Mortensen, Sven:93.5
*/
C#
Use un tipo con nombre si debe pasar la variable de consulta a otro método. Cree una
clase especial usando las propiedades autoimplementadas para las claves y, luego,
invalide los métodos Equals y GetHashCode. También puede usar un struct, en cuyo
caso no es estrictamente necesario invalidar esos métodos. Para más información,
consulte Procedimiento Implementar una clase ligera con propiedades
autoimplementadas y Procedimiento para buscar archivos duplicados en un árbol de
directorios. El último artículo contiene un ejemplo de código en el que se muestra cómo
usar una clave compuesta con un tipo con nombre.
Ejemplo 1
En el ejemplo siguiente se muestra el patrón estándar para ordenar los datos de origen
en grupos cuando no se aplica ninguna lógica de consulta adicional a los grupos. Esto
se denomina agrupación sin continuación. Los elementos de una matriz de cadenas se
agrupan por la primera letra. El resultado de la consulta es un tipo
IGrouping<TKey,TElement> que contiene una propiedad Key pública de tipo char y
una colección IEnumerable<T> que contiene cada elemento de la agrupación.
class GroupExample1
var wordGroups =
from w in words
group w by w[0];
Console.WriteLine(word);
Console.ReadKey();
/* Output:
blueberry
banana
chimpanzee
cheese
abacus
apple
*/
Ejemplo 2
En este ejemplo se muestra cómo aplicar lógica adicional a los grupos después de
haberlos creado, mediante el uso de una continuación con into . Para obtener más
información, vea into. En el ejemplo siguiente se consulta cada grupo para seleccionar
solo aquellos cuyo valor de clave sea una vocal.
C#
class GroupClauseExample2
var wordGroups2 =
from w in words2
select grps;
Console.ReadKey();
/* Output:
abacus
apple
anteater
elephant
umbrella
*/
Comentarios
En tiempo de compilación, las cláusulas group se convierten en llamadas al método
GroupBy.
Consulte también
IGrouping<TKey,TElement>
GroupBy
ThenBy
ThenByDescending
Palabras clave para consultas
Language-Integrated Query (LINQ)
Crear grupos anidados
Agrupar los resultados de consultas
Realizar una subconsulta en una operación de agrupación
into (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
La palabra clave contextual into puede usarse para crear un identificador temporal para
almacenar los resultados de una cláusula group, join o select en un nuevo identificador.
Este identificador puede ser un generador de comandos de consulta adicionales.
Cuando se usa en una cláusula group o select , el uso del nuevo identificador se
denomina a veces una continuación.
Ejemplo
En el ejemplo siguiente, se muestra el uso de la palabra clave into para habilitar un
identificador temporal fruitGroup que tiene un tipo deducido de IGrouping . Mediante
el identificador, puede invocar el método Count en cada grupo y seleccionar solo los
grupos que contienen dos o más palabras.
C#
class IntoSample1
var wordGroups1 =
from w in words
// Execute the query. Note that we only iterate over the groups,
Console.ReadKey();
/* Output:
a has 2 elements.
b has 2 elements.
*/
El uso de into en una cláusula group solo es necesario cuando quiere realizar
operaciones de consulta adicionales en cada grupo. Para obtener más información, vea
group (Cláusula).
Para obtener un ejemplo del uso de into en una cláusula join , vea join (Cláusula).
Vea también
Palabras clave para consultas (LINQ)
LINQ en C#
group (cláusula)
orderby (Cláusula, Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Ejemplo 1
En el ejemplo siguiente, la primera consulta ordena las palabras en orden alfabético a
partir de la A y la segunda consulta ordena las mismas palabras en orden descendente.
(La palabra clave ascending es el valor de ordenación predeterminado y puede
omitirse).
C#
class OrderbySample1
IEnumerable<string> sortAscendingQuery =
select fruit;
IEnumerable<string> sortDescendingQuery =
from w in fruits
orderby w descending
select w;
Console.WriteLine("Ascending:");
Console.WriteLine(s);
}
Console.WriteLine(Environment.NewLine + "Descending:");
Console.WriteLine(s);
}
Console.ReadKey();
/* Output:
Ascending:
apple
blueberry
cherry
Descending:
cherry
blueberry
apple
*/
Ejemplo 2
En el ejemplo siguiente, se realiza una ordenación primaria por apellidos de los alumnos
y, después, una ordenación secundaria por sus nombres.
C#
class OrderbySample2
};
return students;
IEnumerable<Student> sortedStudents =
select student;
Console.WriteLine("sortedStudents:");
// Now create groups and sort the groups. The query first sorts the
names
// grouped. The second orderby sorts the group keys in alpha order.
var sortedGroups =
orderby newGroup.Key
select newGroup;
Console.WriteLine(Environment.NewLine + "sortedGroups:");
Console.WriteLine(studentGroup.Key);
Console.ReadKey();
/* Output:
sortedStudents:
Garcia Cesar
Garcia Debra
Mortensen Sven
O'Donnell Claire
Omelchenko Svetlana
sortedGroups:
Garcia, Cesar
Garcia, Debra
Mortensen, Sven
O'Donnell, Claire
Omelchenko, Svetlana
*/
Comentarios
En tiempo de compilación, la cláusula orderby se convierte en una llamada al método
OrderBy. Varias claves en la cláusula orderby se convierten en llamadas al método
ThenBy.
Vea también
Referencia de C#
Palabras clave para consultas (LINQ)
LINQ en C#
group (cláusula)
Language-Integrated Query (LINQ)
join (Cláusula, Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 10 minutos
La cláusula join es útil para asociar elementos de secuencias de origen diferentes que
no tienen ninguna relación directa en el modelo de objetos. El único requisito es que los
elementos de cada origen compartan algún valor del que se pueda comparar la
igualdad. Por ejemplo, imagínese que un distribuidor de comida tiene una lista de
proveedores de un determinado producto y una lista de compradores. Se puede usar
una cláusula join , por ejemplo, para crear una lista de los proveedores y compradores
de dicho producto que se encuentran en la misma región especificada.
La cláusula join toma dos secuencias de origen como entrada. Los elementos de cada
secuencia deben ser o deben contener una propiedad que se pueda comparar con una
propiedad correspondiente en la otra secuencia. La cláusula join compara la igualdad
de las claves especificadas mediante la palabra clave especial equals . Todas las
combinaciones efectuadas por la cláusula join son combinaciones de igualdad. La
forma de la salida de una cláusula join depende del tipo específico de combinación
que se va a efectuar. Estos son los tres tipos de combinación más comunes:
Combinación interna
Combinación agrupada
Combinación interna
En el ejemplo siguiente se muestra una combinación de igualdad interna simple. Esta
consulta genera una secuencia plana de pares de "nombre de producto y categoría". La
misma cadena de categoría aparecerá en varios elementos. Si un elemento de
categories no tiene ningún products que coincida, dicha categoría no aparecerá en los
resultados.
C#
var innerJoinQuery =
Combinación agrupada
Una cláusula join con una expresión into se denomina "combinación agrupada".
C#
var innerGroupJoinQuery =
Si solo selecciona los resultados de una combinación agrupada, puede tener acceso a
los elementos, pero no podrá identificar la clave en la que coinciden. Por lo tanto, suele
resultar más útil seleccionar los resultados de la combinación agrupada en un nuevo
tipo que también tenga el nombre de la clave, como se muestra en el ejemplo anterior.
Por supuesto, también puede usar el resultado de una combinación agrupada como
generador de otra subconsulta:
C#
var innerGroupJoinQuery2 =
select prod2;
C#
var leftOuterJoinQuery =
El operador de igualdad
La cláusula join efectúa una combinación de igualdad. En otras palabras, solo puede
basar las coincidencias en la igualdad de dos claves. No se admiten otros tipos de
comparaciones, como "mayor que" o "no es igual a". Para aclarar que todas las
combinaciones son combinaciones de igualdad, la cláusula join usa la palabra clave
equals en lugar del operador == . La palabra clave equals solo se puede usar en una
Combinaciones de desigualdad
Puede efectuar combinaciones de desigualdad, combinaciones cruzadas y otras
operaciones de combinación personalizadas usando varias cláusulas from para
introducir nuevas secuencias en una consulta de manera independiente. Para obtener
más información, vea Realizar operaciones de combinación personalizadas.
Para más información sobre las consultas en tablas relacionadas en el contexto de LINQ
to SQL, consulte Cómo asignar relaciones de bases de datos.
Claves compuestas
Puede probar la igualdad de varios valores mediante una clave compuesta. Para obtener
más información, vea Realizar una unión usando claves compuestas. Las claves
compuestas también se pueden usar en una cláusula group .
Ejemplo
En el ejemplo siguiente se comparan los resultados de una combinación interna, una
combinación agrupada y una combinación externa izquierda en los mismos orígenes de
datos mediante las mismas claves coincidentes. Se ha agregado algún código adicional
a estos ejemplos para aclarar los resultados en la pantalla de la consola.
C#
class JoinDemonstration
#region Data
class Product
class Category
};
};
#endregion
app.InnerJoin();
app.GroupJoin();
app.GroupInnerJoin();
app.GroupJoin3();
app.LeftOuterJoin();
app.LeftOuterJoin2();
Console.ReadKey();
void InnerJoin()
var innerJoinQuery =
Console.WriteLine("InnerJoin:");
Console.WriteLine(System.Environment.NewLine);
void GroupJoin()
var groupJoinQuery =
select prodGroup;
int totalItems = 0;
Console.WriteLine("Simple GroupJoin:");
Console.WriteLine("Group:");
totalItems++;
Console.WriteLine(System.Environment.NewLine);
void GroupInnerJoin()
var groupJoinQuery2 =
orderby category.ID
select new
Category = category.Name,
orderby prod2.Name
select prod2
};
//Console.WriteLine("GroupInnerJoin:");
int totalItems = 0;
Console.WriteLine("GroupInnerJoin:");
Console.WriteLine(productGroup.Category);
totalItems++;
Console.WriteLine(System.Environment.NewLine);
void GroupJoin3()
var groupJoinQuery3 =
orderby prod.CategoryID
//Console.WriteLine("GroupInnerJoin:");
int totalItems = 0;
Console.WriteLine("GroupJoin3:");
totalItems++;
Console.WriteLine(System.Environment.NewLine);
void LeftOuterJoin()
var leftOuterQuery =
int totalItems = 0;
Console.WriteLine("Group:");
totalItems++;
Console.WriteLine(System.Environment.NewLine);
void LeftOuterJoin2()
var leftOuterQuery2 =
int totalItems = 0;
totalItems++;
/*Output:
InnerJoin:
Cola 1
Tea 1
Mustard 2
Pickles 2
Carrots 3
Bok Choy 3
Peaches 5
Melons 5
Unshaped GroupJoin:
Group:
Cola 1
Tea 1
Group:
Mustard 2
Pickles 2
Group:
Carrots 3
Bok Choy 3
Group:
Group:
Peaches 5
Melons 5
GroupInnerJoin:
Beverages
Cola 1
Tea 1
Condiments
Mustard 2
Pickles 2
Vegetables
Bok Choy 3
Carrots 3
Grains
Fruit
Melons 5
Peaches 5
GroupJoin3:
Cola:1
Tea:1
Mustard:2
Pickles:2
Carrots:3
Bok Choy:3
Peaches:5
Melons:5
Group:
Cola 1
Tea 1
Group:
Mustard 2
Pickles 2
Group:
Carrots 3
Bok Choy 3
Group:
Nothing! 4
Group:
Peaches 5
Melons 5
Cola 1
Tea 1
Mustard 2
Pickles 2
Carrots 3
Bok Choy 3
Nothing! 4
Peaches 5
Melons 5
*/
Comentarios
Una cláusula join que no va seguida de into se convierte en una llamada al método
Join. Una cláusula join que va seguida de into se convierte en una llamada al método
GroupJoin.
Vea también
Palabras clave para consultas (LINQ)
Language-Integrated Query (LINQ)
Operaciones de combinación
group (cláusula)
Realizar operaciones de combinación externa izquierda
Realizar combinaciones internas
Realizar combinaciones agrupadas
Ordenar los resultados de una cláusula join
Realizar una unión usando claves compuestas
Sistemas de base de datos compatible para Visual Studio
let (Cláusula, Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Ejemplo
En el siguiente ejemplo, se usa let de dos maneras:
2. Para habilitar la consulta para que llame a ToLower solo una vez en la variable de
rango word . Sin usar let , tendría que llamar a ToLower en cada predicado de la
cláusula where .
C#
class LetSample1
string[] strings =
};
var earlyBirdQuery =
let w = word.ToLower()
|| w[0] == 'u'
select word;
Console.ReadKey();
/* Output:
*/
Vea también
Referencia de C#
Palabras clave para consultas (LINQ)
LINQ en C#
Language-Integrated Query (LINQ)
Controlar excepciones en expresiones de consulta
ascending (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Ejemplo
En el ejemplo siguiente se muestra el uso de ascending en una cláusula orderby.
C#
IEnumerable<string> sortAscendingQuery =
select vegetable;
Vea también
Referencia de C#
LINQ en C#
descending
descending (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Ejemplo
En el ejemplo siguiente se muestra el uso de descending en una cláusula orderby.
C#
IEnumerable<string> sortDescendingQuery =
select vegetable;
Vea también
Referencia de C#
LINQ en C#
ascending
on (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Ejemplo
En el ejemplo siguiente se muestra el uso de on en una cláusula join .
C#
var innerJoinQuery =
Vea también
Referencia de C#
Language-Integrated Query (LINQ)
equals (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
La palabra clave contextual equals se usa en una cláusula join en una expresión de
consulta para comparar los elementos de dos secuencias. Para obtener más información,
vea join (Cláusula, Referencia de C#).
Ejemplo
En el ejemplo siguiente se muestra el uso de la palabra clave equals en una cláusula
join .
C#
var innerJoinQuery =
Vea también
Language-Integrated Query (LINQ)
by (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Ejemplo
En el ejemplo siguiente se muestra el uso de la palabra clave contextual by en una
cláusula group para especificar que los estudiantes deben agruparse según la primera
letra del apellido de cada estudiante.
C#
Consulte también
LINQ en C#
in (Referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Vea también
Palabras clave de C#
Referencia de C#
Operadores y expresiones de C#
(referencia de C#)
Artículo • 15/02/2023 • Tiempo de lectura: 5 minutos
C# proporciona una serie de operadores. Muchos de ellos son compatibles con los tipos
integrados y permiten realizar operaciones básicas con valores de esos tipos. Entre estos
operadores se incluyen los siguientes grupos:
Las expresiones más simples de C# son literales (por ejemplo, números enteros y reales)
y nombres de variables. Puede combinarlas para crear expresiones complejas mediante
el uso de operadores. La precedencia y la asociatividad de los operadores determinan el
orden en el que se realizan las operaciones en una expresión. Puede usar los paréntesis
para cambiar el orden de evaluación impuesto por la prioridad y la asociatividad de
operadores.
C#
int a, b, c;
a = 7;
b = a;
c = b++;
b = a + b * c;
a = (int)Math.Sqrt(b * b + c * c);
C#
Console.WriteLine("Hello, world!");
C#
var r = 2.3;
Console.WriteLine(message);
// Output:
C#
int[] numbers = { 2, 3, 4, 5 };
Console.WriteLine(maximumSquare);
// Output:
// 25
C#
IEnumerable<int> highScoresQuery =
select score;
// Output:
// 97 90 85
Puede usar una definición de cuerpo de expresiones para proporcionar una definición
concisa para un método, un constructor, una propiedad, un indexador o un finalizador.
Prioridad de operadores
En una expresión con varios operadores, los operadores con mayor prioridad se evalúan
antes que los operadores con menor prioridad. En el ejemplo siguiente, la multiplicación
se realiza primero porque tiene mayor prioridad que la suma:
C#
var a = 2 + 2 * 2;
Console.WriteLine(a); // output: 6
Use paréntesis para cambiar el orden de evaluación impuesto por la prioridad de los
operadores:
C#
var a = (2 + 2) * 2;
Console.WriteLine(a); // output: 8
x.y, f(x), a[i], x?.y, x?[y], x++, x--, x!, new, typeof, checked, Principal
unchecked, default, nameof, delegate, sizeof, stackalloc, x->y
+x, -x, !x, ~x, ++x, --x, ^x, (T)x, await, &x, *x, true and false Unario
x..y Intervalo
x * y, x / y, x % y Multiplicativo
x + y, x – y Aditivo
x == y, x != y Igualdad
x || y OR condicional
C#
int a = 13 / 5 / 2;
int b = 13 / (5 / 2);
Evaluación de operandos
Independientemente de la prioridad y la asociatividad de los operadores, los operandos
de una expresión se evalúan de izquierda a derecha. En los siguientes ejemplos, se
muestra el orden en el que se evalúan los operadores y los operandos:
a + b a, b, +
a + b * c a, b, c, *, +
a / b + c * d a, b, /, c, d, *, +
a / (b + c) * d a, b, c, +, /, d, *
Expresiones
Operadores
Vea también
Referencia de C#
Sobrecarga de operadores
Árboles de expresión
Operadores aritméticos (referencia de
C#)
Artículo • 15/02/2023 • Tiempo de lectura: 12 minutos
Estos operadores se admiten en todos los tipos numéricos enteros y de punto flotante.
Operador de incremento ++
El operador de incremento unario ++ incrementa su operando en 1. El operando debe
ser una variable, un acceso de propiedad o un acceso de indexador.
C#
int i = 3;
Console.WriteLine(i); // output: 3
Console.WriteLine(i++); // output: 3
Console.WriteLine(i); // output: 4
C#
double a = 1.5;
Operador de decremento --
El operador de decremento unario -- disminuye su operando en 1. El operando debe
ser una variable, un acceso de propiedad o un acceso de indexador.
C#
int i = 3;
Console.WriteLine(i); // output: 3
Console.WriteLine(i--); // output: 3
Console.WriteLine(i); // output: 2
C#
double a = 1.5;
C#
Console.WriteLine(+4); // output: 4
Console.WriteLine(-4); // output: -4
Console.WriteLine(-(-4)); // output: 4
uint a = 5;
var b = -a;
Console.WriteLine(b); // output: -5
Operador de multiplicación *
El operador de multiplicación * calcula el producto de sus operandos:
C#
Operador de división /
El operador de división / divide el operando izquierdo entre el derecho.
División de enteros
Para los operandos de tipos enteros, el resultado del operador / es de un tipo entero y
equivale al cociente de los dos operandos redondeados hacia cero:
C#
Para obtener el cociente de los dos operandos como número de punto flotante, use el
tipo float , double o decimal :
C#
int a = 13;
int b = 5;
C#
Si uno de los operandos es decimal , otro operando no puede ser float ni double , ya
que ni float ni double se convierte de forma implícita a decimal . Debe convertir
explícitamente el operando float o double al tipo decimal . Para obtener más
información sobre las conversiones entre tipos numéricos, consulte Conversiones
numéricas integradas.
Operador de resto %
El operador de resto % calcula el resto después de dividir el operando izquierdo entre el
derecho.
Resto entero
En el caso de los operandos de tipos enteros, el resultado de a % b es el valor
producido por a - (a / b) * b . El signo de resto distinto de cero es el mismo que el
del operando izquierdo, como se muestra en el ejemplo siguiente:
C#
Use el método Math.DivRem para calcular los resultados de la división de enteros y del
resto.
7 Nota
Este método de cálculo del resto es análogo al que se usa para los operandos
enteros, pero difiere de la especificación IEEE 754. Si necesita la operación de resto
conforme con la especificación IEEE 754, use el método Math.IEEERemainder.
C#
Operador de suma +
El operador de suma + calcula la suma de sus operandos:
C#
Operador de resta -
El operador de resta - resta el operando derecho del izquierdo:
C#
También puede usar el operador - para la eliminación de delegados. Para obtener más
información, consulte Operadores - y -=.
Asignación compuesta
Para un operador binario op , una expresión de asignación compuesta con el formato
C#
x op= y
es equivalente a
C#
x = x op y
C#
int a = 5;
a += 9;
Console.WriteLine(a); // output: 14
a -= 4;
Console.WriteLine(a); // output: 10
a *= 2;
Console.WriteLine(a); // output: 20
a /= 4;
Console.WriteLine(a); // output: 5
a %= 3;
Console.WriteLine(a); // output: 2
C#
byte a = 200;
byte b = 100;
var c = a + b;
a += b;
Console.WriteLine(a); // output: 44
Use los paréntesis, () , para cambiar el orden de evaluación impuesto por la prioridad y
la asociatividad de operadores.
C#
Junto con las instrucciones comprobadas y no comprobadas, puede usar los operadores
checked y unchecked para controlar el contexto de comprobación de desbordamiento,
C#
int a = int.MaxValue;
int b = 3;
try
catch(OverflowException)
C#
Para los operandos del tipo decimal , el desbordamiento aritmético siempre inicia una
excepción OverflowException. La división decimal por cero siempre produce una
excepción DivideByZeroException.
Errores de redondeo
Debido a las limitaciones generales de la representación de punto flotante de los
números reales y la aritmética de punto flotante, es posible que se produzcan errores de
redondeo en los cálculos con tipos de punto flotante. Es decir, es posible que el
resultado de una expresión difiera del resultado matemático esperado. En el ejemplo
siguiente se muestran varios de estos casos:
C#
double a = 0.1;
double b = 3 * a;
decimal c = 1 / 3.0m;
decimal d = 3 * c;
C#
checked
Operadores ++ unarios, -- y -
Operadores binarios * , / , + y -
Operadores de conversión explícitos
7 Nota
Vea también
Referencia de C#
Operadores y expresiones de C#
System.Math
System.MathF
Valores numéricos en .NET
Operadores lógicos booleanos: AND,
OR, NOT, XOR
Artículo • 18/02/2023 • Tiempo de lectura: 8 minutos
Los operadores booleanos lógicos realizan operaciones lógicas con operandos bool. Los
operadores incluyen la negación lógica unaria ( ! ), AND lógico binario ( & ), OR ( | ) y OR
exclusivo ( ^ ) y los AND ( && ) y OR ( || ) lógicos condicionales binarios.
En el caso de los operandos de los tipos numéricos enteros, los operadores & , | y ^
realizan operaciones lógicas bit a bit. Para obtener más información, vea Operadores de
desplazamiento y bit a bit.
C#
El operador & evalúa ambos operandos, incluso aunque el izquierdo se evalúe como
false , para que el resultado de la operación sea false con independencia del valor del
operando derecho.
C#
bool SecondOperand()
return true;
Console.WriteLine(a);
// Output:
// False
Console.WriteLine(b);
// Output:
// True
El operador AND lógico condicional && también calcula el operador AND lógico de sus
operandos, pero no evalúa el operando derecho si el izquierdo se evalúa como false .
En el caso de los operandos de los tipos numéricos enteros, el operador & calcula el
AND lógico bit a bit de sus operandos. El operador & unario es el operador address-of.
C#
Operador lógico OR |
El operador | calcula el operador OR lógico de sus operandos. El resultado de x | y es
true si x o y se evalúan como true . De lo contrario, el resultado es false .
operando derecho.
C#
bool SecondOperand()
return true;
Console.WriteLine(a);
// Output:
// True
Console.WriteLine(b);
// Output:
// True
C#
bool SecondOperand()
return true;
Console.WriteLine(a);
// Output:
// False
Console.WriteLine(b);
// Output:
// True
El operador AND lógico & también calcula el operador AND lógico de sus operandos,
pero siempre evalúa ambos operandos.
C#
bool SecondOperand()
return true;
Console.WriteLine(a);
// Output:
// True
Console.WriteLine(b);
// Output:
// True
El operador & produce true solo si sus dos operandos se evalúan como true . Si
x o y se evalúan como false , x & y produce false (aunque otro operando se
evalúe como null ). De lo contrario, el resultado de x & y es null .
El operador | produce false solo si sus dos operandos se evalúan como false . Si
x o y se evalúan como true , x | y produce true (aunque otro operando se
evalúe como null ). De lo contrario, el resultado de x | y es null .
x y x&y x|y
También puede usar los operadores ! y ^ con los operandos bool? , como se muestra
en el ejemplo siguiente:
C#
Asignación compuesta
Para un operador binario op , una expresión de asignación compuesta con el formato
C#
x op= y
es equivalente a
C#
x = x op y
C#
test |= true;
test ^= false;
7 Nota
Prioridad de operadores
En la lista siguiente se ordenan los operadores lógicos desde la prioridad más alta a la
más baja:
Use los paréntesis, () , para cambiar el orden de evaluación impuesto por la prioridad
de los operadores:
C#
return value;
Console.WriteLine(byDefaultPrecedence);
// Output:
// Operand A is evaluated.
// True
Console.WriteLine(changedOrder);
// Output:
// Operand A is evaluated.
// Operand C is evaluated.
// False
Los operadores bit a bit y shift incluyen complemento unario bit a bit, desplazamiento
binario a la izquierda y derecha, desplazamiento a la derecha sin signo, y los operadores
OR lógicos binarios AND, OR y OR exclusivos. Estos operandos toman operandos de los
tipos numéricos enteros o del tipo char .
Estos operadores se definen para los tipos int , uint , long y ulong . Cuando ambos
operandos son de otro tipo entero ( sbyte , byte , short , ushort o char ), sus valores se
convierten en el tipo int , que también es el tipo de resultado de una operación.
Cuando los operandos son de tipo entero diferente, sus valores se convierten en el tipo
entero más cercano que contenga. Para obtener más información, vea la sección
Promociones numéricas de Especificación del lenguaje C#. Los operadores compuestos
(como >>= ) no convierten sus argumentos en int o tienen el tipo de resultado como
int .
Los operadores & , | y ^ también se definen para los operandos de tipo bool . Para
obtener más información, vea Operadores lógicos booleanos.
C#
uint a = 0b_0000_1111_0000_1111_0000_1111_0000_1100;
uint b = ~a;
// Output:
// 11110000111100001111000011110011
También se puede usar el símbolo ~ para declarar finalizadores. Para obtener más
información, vea Finalizadores.
La operación de desplazamiento izquierdo descarta los bits de orden superior que están
fuera del rango del tipo de resultado y establece las posiciones de bits vacías de orden
inferior en cero, como se muestra en el ejemplo siguiente:
C#
uint x = 0b_1100_1001_0000_0000_0000_0000_0001_0001;
uint y = x << 4;
// Output:
// Before: 11001001000000000000000000010001
// After: 10010000000000000000000100010000
Dado que los operadores de desplazamiento solo se definen para los tipos int , uint ,
long y ulong , el resultado de una operación siempre contiene al menos 32 bits. Si el
operando izquierdo es de otro tipo entero ( sbyte , byte , short , ushort o char ), su valor
se convierte al tipo int , como se muestra en el ejemplo siguiente:
C#
byte a = 0b_1111_0001;
var b = a << 8;
Console.WriteLine(b.GetType());
// Output:
// System.Int32
C#
uint x = 0b_1001;
uint y = x >> 2;
// Output:
// Before: 1001
// After: 0010
Las posiciones de bits vacíos de orden superior se establecen basándose en el tipo del
operando izquierdo, tal como se indica a continuación:
C#
int a = int.MinValue;
int b = a >> 3;
// Output:
// Before: 10000000000000000000000000000000
// After: 11110000000000000000000000000000
C#
uint c = 0b_1000_0000_0000_0000_0000_0000_0000_0000;
uint d = c >> 3;
// Output:
// Before: 10000000000000000000000000000000
// After: 00010000000000000000000000000000
7 Nota
C#
int x = -8;
int y = x >> 2;
int z = x >>> 2;
// Output:
C#
uint a = 0b_1111_1000;
uint b = 0b_1001_1101;
uint c = a & b;
// Output:
// 10011000
Para los operandos bool , el operador & calcula el AND lógico de sus operandos. El
operador & unario es el operador address-of.
C#
uint a = 0b_1111_1000;
uint b = 0b_0001_1100;
uint c = a ^ b;
// Output:
// 11100100
Operador lógico OR |
El operador | calcula el OR lógico bit a bit de sus operandos enteros:
C#
uint a = 0b_1010_0000;
uint b = 0b_1001_0001;
uint c = a | b;
// Output:
// 10110001
Asignación compuesta
Para un operador binario op , una expresión de asignación compuesta con el formato
C#
x op= y
es equivalente a
C#
x = x op y
C#
uint a = INITIAL_VALUE;
a &= 0b_1001_1101;
a = INITIAL_VALUE;
a |= 0b_0011_0001;
a = INITIAL_VALUE;
a ^= 0b_1000_0000;
a = INITIAL_VALUE;
a <<= 2;
a = INITIAL_VALUE;
a >>= 4;
a = INITIAL_VALUE;
a >>>= 4;
C#
byte x = 0b_1111_0001;
int b = x << 8;
x <<= 8;
Console.WriteLine(x); // output: 0
Prioridad de operadores
En la lista siguiente se ordenan los operadores de desplazamiento y bit a bit desde la
prioridad más alta a la más baja:
C#
uint a = 0b_1101;
uint b = 0b_1001;
uint c = 0b_1010;
uint d1 = a | b & c;
uint d2 = (a | b) & c;
Para las expresiones de x << count , x >> count y x >>> count , el recuento de
desplazamientos real depende del tipo de x de la siguiente manera:
C#
int a = 0b_0001;
// Output:
int b = 0b_0100;
// Output:
7 Nota
Normalmente, los operadores lógicos bit a bit se usan con un tipo de enumeración
definido con el atributo Flags. Para obtener más información, vea la sección Tipos de
enumeración como marcas de bits del artículo Tipos de enumeración.
Si un tipo definido por el usuario T sobrecarga el operador << , >> o >>> , el tipo del
operando izquierdo debe ser T . En C# 10 y versiones anteriores, el tipo del operando
derecho debe ser int ; a partir de C# 11, el tipo del operando derecho de un operador
de desplazamiento sobrecargado puede ser cualquiera.
especificación del lenguaje C#
Para más información, vea las secciones siguientes de la Especificación del lenguaje C#:
Vea también
Referencia de C#
Operadores y expresiones de C#
Operadores lógicos booleanos
Operadores de igualdad: prueban si dos
objetos son iguales o no
Artículo • 17/02/2023 • Tiempo de lectura: 5 minutos
Operador de igualdad ==
El operador de igualdad == devuelve true si sus operandos son iguales; en caso
contrario, devuelve false .
C#
int a = 1 + 2 + 3;
int b = 6;
char c1 = 'a';
char c2 = 'A';
7 Nota
Para los operadores == , <, >, <= y >=, si cualquier operando no es un número
(Double.NaN o Single.NaN), el resultado del operador será false . Esto significa
que el valor NaN no es mayor, inferior ni igual que cualquier otro valor double o
float , incluido NaN . Para obtener más información y ejemplos, vea el artículo de
referencia Double.NaN o Single.NaN.
Dos operandos del mismo tipo enum son iguales si los valores correspondientes del
tipo entero subyacente son iguales.
Los tipos struct definidos por el usuario no son compatibles con el operador == de
forma predeterminada. Para admitir el operador == , un elemento struct definido por el
usuario debe sobrecargarlo.
Los operadores == y != son compatibles con las tuplas de C#. Si desea más
información, consulte la sección Igualdad de tupla del artículo Tipos de tupla.
C#
var c = a;
C#
C#
string s1 = "hello!";
string s2 = "HeLLo!";
string s3 = "Hello!";
C#
Action b = a + a;
Action c = a + a;
Para obtener más información, vea la sección sobre los operadores de igualdad entre
delegados de la Especificación del lenguaje C#.
C#
Operador de desigualdad !=
El operador de desigualdad ( != ) devuelve true si sus operandos no son iguales; en
caso contrario, devuelve false . Para los operandos de los tipos integrados, la expresión
x != y genera el mismo resultado que la expresión !(x == y) . Para obtener más
información sobre la igualdad de tipos, vea la sección Operador de igualdad.
C#
int a = 1 + 1 + 2 + 3;
int b = 6;
string s1 = "Hello";
string s2 = "Hello";
object o1 = 1;
object o2 = 1;
C#
Para obtener más información sobre la igualdad de los tipos de registro, vea la sección
Miembros de igualdad de la nota de propuesta de características de registros.
Vea también
Referencia de C#
Operadores y expresiones de C#
System.IEquatable<T>
Object.Equals
Object.ReferenceEquals
Comparaciones de igualdad
Operadores de comparación
Operadores de comparación (referencia
de C#)
Artículo • 17/02/2023 • Tiempo de lectura: 2 minutos
Los operadores de la comparación < (menor que), > (mayor que), <= (menor o igual
que) y >= (mayor o igual que), también conocidos como relacionales, comparan sus
operandos. Estos operadores se admiten en todos los tipos numéricos enteros y de
punto flotante.
7 Nota
Para los operadores == , < , > , <= y >= , si cualquier operando no es un número
(Double.NaN o Single.NaN), el resultado del operador será false . Esto significa
que el valor NaN no es mayor, inferior ni igual que cualquier otro valor double o
float , incluido NaN . Para obtener más información y ejemplos, vea el artículo de
referencia Double.NaN o Single.NaN.
C#
C#
C#
C#
Si un tipo sobrecarga uno de los operadores < o > , también debe sobrecargar < y > . Si
un tipo sobrecarga uno de los operadores <= o >= , también debe sobrecargar <= y >= .
Vea también
Referencia de C#
Operadores y expresiones de C#
System.IComparable<T>
Operadores de igualdad
Operadores y expresiones de acceso a
miembros: los operadores de punto,
indexador e invocación.
Artículo • 15/02/2023 • Tiempo de lectura: 11 minutos
Use varios operadores y expresiones para tener acceso a un miembro de tipo. Estos
operadores incluyen el acceso a miembros ( . ), el elemento de matriz o el acceso al
indizador ( [] ), el índice desde el extremo (), el intervalo ( .. ^ ), los operadores
condicionales NULL ( ?. y ) y ?[] la invocación del método ( () ). Entre ellas se incluyen
los operadores de acceso a miembros condicionales NULL ( .? ) e acceso de indexador ( ?
[] ).
C#
using System.Collections.Generic;
Use . para formar un nombre completo para tener acceso a un tipo dentro de un
espacio de nombres, como se muestra en el código siguiente:
C#
Utilice una directiva using para hacer que el uso de nombres completos sea opcional.
Use . para tener acceso a los miembros de tipo, que no son estáticos y, como se
muestra en el código siguiente:
C#
constants.Add(Math.PI);
constants.Add(Math.E);
// Output:
// 2 values to show:
// 3.14159265358979, 2.71828182845905
Acceso a matriz
En el ejemplo siguiente se muestra cómo se obtiene acceso a los elementos de matriz:
C#
fib[0] = fib[1] = 1;
matrix[0,0] = 1.0;
matrix[0,1] = 2.0;
Console.WriteLine(determinant); // output: -3
Tal como se muestra en el ejemplo anterior, también usa corchetes al declarar un tipo
de matriz o crear instancias de matriz.
Acceso a indizador
En el ejemplo siguiente se usa el tipo Dictionary<TKey,TValue> de .NET para mostrar el
acceso al indizador:
C#
dict["one"] = 1;
dict["pi"] = Math.PI;
Los indizadores le permiten indizar las instancias de un tipo definido por el usuario de
un modo similar a la indización de matrices. A diferencia de los índices de matriz, que
deben ser enteros, los parámetros de indizador se pueden declarar para ser de cualquier
tipo.
Otros usos de []
Para información sobre el acceso de los elementos de puntero, consulte la sección
Operador de acceso de elemento de puntero del artículo Operadores relacionados con
el puntero.
C#
[System.Diagnostics.Conditional("DEBUG")]
void TraceMethod() {}
7 Nota
C#
A?.B?.Do(C);
A?.B?[C];
Si A podría ser NULL pero B y C no sería NULL si A no es NULL, solo tiene que aplicar el
operador condicional null a A :
C#
A?.B.C();
C#
double SumNumbers(List<double[]> setsOfNumbers, int indexOfSetToSum)
null
};
Console.WriteLine(sum2); // output: 6
C#
namespace MemberAccessOperators2;
try
(person?.Name).Write();
catch (NullReferenceException)
Console.WriteLine("NullReferenceException");
}; // output: NullReferenceException
Console.WriteLine($"{FirstName} {LastName}");
Si a.x o a[x] es de un tipo de valor que no admite un valores NULL, T , a?.x o a?[x]
es del tipo de valor que admite un valor NULL T? correspondiente. Si necesita una
expresión de tipo T , aplique el operador de fusión de NULL ?? a una expresión
condicional NULL, tal como se muestra en el ejemplo siguiente:
C#
if ((numbers?.Length ?? 0) < 2)
return 0;
Console.WriteLine(GetSumOfFirstTwoOrDefault(null)); // output: 0
7 Nota
C#
PropertyChanged?.Invoke(…)
C#
if (handler != null)
handler(…);
El ejemplo anterior es una manera segura para subprocesos para asegurarse de que
solo se invoca un valor distinto de NULL handler . Dado que las instancias de delegado
son inmutables, ningún subproceso puede cambiar el valor al que hace referencia la
variable local handler . En concreto, si el código que ha ejecutado otro subproceso
cancela la suscripción del evento PropertyChanged y PropertyChanged se convierte en
null antes de que se invoque handler , el objeto al que hace referencia handler queda
intacto.
Expresión de invocación ()
Utilice paréntesis, () , para llamar a un método o invocar un delegado.
C#
numbers.Add(10);
numbers.Add(17);
display(numbers.Count); // output: 2
numbers.Clear();
display(numbers.Count); // output: 0
Otros usos de ()
También usa los paréntesis para ajustar el orden en el que se van a evaluar operaciones
en una expresión. Para obtener más información, vea Operadores de C# (referencia de
C#).
C#
Console.WriteLine(last); // output: 40
Console.WriteLine(first); // output: T
También puede usar el operador ^ con el operador de intervalo para crear un intervalo
de índices. Para más información, consulte Índices y rangos.
Operador Range ..
El operador .. especifica el inicio y el final de un intervalo de índices como sus
operandos. El operando izquierdo es un inicio inclusivo de un intervalo. El operando
derecho es un inicio exclusivo de un intervalo. Cualquiera de los operandos puede ser un
índice desde el inicio o desde el final de una secuencia, tal y como muestra el ejemplo
siguiente:
C#
int start = 1;
int amountToTake = 3;
Display(subset); // output: 10 20 30
int margin = 1;
Display(inner); // output: 10 20 30 40
int amountToTakeFromEnd = 5;
) Importante
Puede omitir cualquiera de los operandos del operador .. para obtener un intervalo
abierto:
C#
Display(rightHalf); // output: 30 40 50
Display(leftHalf); // output: 0 10 20
Display(all); // output: 0 10 20 30 40 50
C#
int[] oneThroughTen =
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
};
Write(oneThroughTen, ..);
Write(oneThroughTen, ..3);
Write(oneThroughTen, 2..);
Write(oneThroughTen, 3..5);
Write(oneThroughTen, ^2..);
Write(oneThroughTen, ..^3);
Write(oneThroughTen, 3..^4);
Write(oneThroughTen, ^4..^2);
// Sample output:
// 0..^0: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
// 0..3: 1, 2, 3
// 2..^0: 3, 4, 5, 6, 7, 8, 9, 10
// 3..5: 4, 5
// ^2..^0: 9, 10
// 0..^3: 1, 2, 3, 4, 5, 6, 7
// 3..^4: 4, 5, 6
// ^4..^2: 7, 8
Acceso a miembros
Acceso a elementos
El operador "null conditional member access"
Expresiones de invocación
Vea también
Uso del operador "index" (regla de estilo IDE0056)
Uso del operador "range" (regla de estilo IDE0057)
Uso de la llamada de delegado condicional (regla de estilo IDE1005)
Referencia de C#
Operadores y expresiones de C#
?? (operador de fusión de NULL)
Operador ::
Operadores de prueba de tipos y
expresión de conversión: is , as , typeof
y conversiones
Artículo • 17/02/2023 • Tiempo de lectura: 7 minutos
Operador is
El operador is comprueba si el tipo en tiempo de ejecución del resultado de una
expresión es compatible con un tipo determinado. El operador is también prueba el
resultado de una expresión en relación con un patrón.
C#
E is T
El tipo en tiempo de ejecución del resultado de una expresión deriva del tipo T ,
implementa una interfaz T , o bien otra conversión de referencia implícita existe en
T.
C#
C#
int i = 27;
object iBoxed = i;
Para obtener información acerca de las conversiones de C#, vea el capítulo Conversiones
de la especificación del lenguaje C#.
C#
int i = 23;
object iBoxed = i;
int? jNullable = 7;
Operador as
El operador as convierte explícitamente el resultado de una expresión en una referencia
determinada o un tipo de valor que acepta valores NULL. Si la conversión no es posible,
el operador as devuelve null . A diferencia de la expresión Cast, el operador as no
genera nunca una excepción.
C#
E as T
C#
E is T ? (T)(E) : (T)null
El operador as solo considera las conversiones de referencia, las que aceptan valores
NULL, boxing y unboxing. No puede usar el operador as para realizar una conversión
definida por el usuario. Para ello, use una expresión Cast.
if (indexable != null)
7 Nota
Expresión Cast
Una expresión de conversión con el formato (T)E realiza una conversión explícita del
resultado de la expresión E al tipo T . Si no existe ninguna conversión explícita del tipo
de E al tipo T , se producirá un error en tiempo de compilación. En el tiempo de
ejecución, una conversión explícita podría no completarse correctamente y una
expresión de conversión podría generar una excepción.
C#
double x = 1234.7;
int a = (int)x;
Console.WriteLine(list.Count); // output: 3
Console.WriteLine(list[1]); // output: 20
Para obtener más información sobre las conversiones explícitas, vea la sección
Conversiones explícitas de la especificación del lenguaje C#. Para obtener información
sobre cómo definir una conversión personalizada de tipo explícito o implícito, vea
Operadores de conversión definidos por el usuario.
Otros usos de ()
También puede utilizar paréntesis para llamar a un método o invocar un delegado.
Sirven además para ajustar el orden en el que se van a evaluar operaciones en una
expresión. Para obtener más información, vea Operadores de C# (referencia de C#).
typeof (operador)
El operador typeof obtiene la instancia System.Type para un tipo. El argumento del
operador typeof debe ser el nombre de un tipo o un parámetro de tipo, como se
muestra en el ejemplo siguiente:
C#
Console.WriteLine(typeof(List<string>));
PrintType<int>();
PrintType<System.Int32>();
PrintType<Dictionary<int, char>>();
// Output:
// System.Collections.Generic.List`1[System.String]
// System.Int32
// System.Int32
// System.Collections.Generic.Dictionary`2[System.Int32,System.Char]
dynamic
También se puede usar el operador typeof con tipos genéricos sin enlazar. El nombre
de un tipo genérico sin enlazar debe contener el número apropiado de comas, que es
inferior en una unidad al número de parámetros de tipo. En el siguiente ejemplo se
muestra el uso del operador typeof con un tipo genérico sin enlazar:
C#
Console.WriteLine(typeof(Dictionary<,>));
// Output:
// System.Collections.Generic.Dictionary`2[TKey,TValue]
Una expresión no puede ser un argumento del operador typeof . Para obtener la
instancia de System.Type para el tipo en tiempo de ejecución del resultado de una
expresión, use el método Object.GetType.
C#
El operador is
El operador as
Expresiones de conversión
El operador typeof
Vea también
Referencia de C#
Operadores y expresiones de C#
Procedimiento para convertir de forma segura mediante la coincidencia de
patrones y los operadores is y as
Elementos genéricos en .NET
Operadores de conversión explícitos e
implícitos definidos por el usuario
Artículo • 18/02/2023 • Tiempo de lectura: 2 minutos
Un tipo definido por el usuario puede definir una conversión implícita o explícita
personalizada desde o a otro tipo. Las conversiones implícitas no requieren que se
invoque una sintaxis especial y pueden producirse en diversas situaciones, por ejemplo,
en las invocaciones de métodos y asignaciones. Las conversiones implícitas predefinidas
en C# siempre se realizan correctamente y nunca inician una excepción. Las
conversiones implícitas definidas por el usuario deben comportarse de esta manera. Si
una conversión personalizada puede producir una excepción o perder información, se
define como una conversión explícita.
Los operadores is y as no tienen en cuenta las conversiones definidas por el usuario. Use
una expresión Cast para invocar una conversión explícita definida por el usuario.
Use las palabras clave operator y implicit o explicit para definir una conversión
implícita o explícita, respectivamente. El tipo que define una conversión debe ser un tipo
de origen o un tipo de destino de dicha conversión. Una conversión entre dos tipos
definidos por el usuario se puede definir en cualquiera de los dos tipos.
C#
using System;
if (digit > 9)
this.digit = digit;
byte number = d;
Console.WriteLine(number); // output: 7
Console.WriteLine(digit); // output: 7
Use también la palabra clave operator para sobrecargar un operador predefinido en C#.
Para obtener más información, vea Sobrecarga de operadores.
Operadores de conversión
Conversiones definidas por el usuario
Conversiones implícitas
Conversiones explícitas
Vea también
Referencia de C#
Operadores y expresiones de C#
Sobrecarga de operadores
Operadores de conversión y prueba de tipos
Conversiones de tipos
Directrices de diseño: operadores de conversión
Chained user-defined explicit conversions in C# (Conversiones explícitas
encadenadas definidas por el usuario en C#)
Operadores relacionados con el
puntero: tome la dirección de las
variables, las ubicaciones de
almacenamiento de desreferencia y las
ubicaciones de memoria de acceso
Artículo • 17/02/2023 • Tiempo de lectura: 8 minutos
Para obtener información sobre los tipos de punteros, vea Tipos de puntero.
7 Nota
C#
unsafe
El operando del operador & debe ser una variable fija. Las variables fijas son las que
residen en ubicaciones de almacenamiento que no se ven afectadas por el
funcionamiento del recolector de elementos no utilizados. En el ejemplo anterior, la
variable local number es una variable fija, ya que reside en la pila. Las variables que
residen en ubicaciones de almacenamiento que pueden verse afectadas por el
recolector de elementos no utilizados (por ejemplo, reubicadas) se denominan variables
móviles. Los campos de objeto y los elementos de matriz son ejemplos de variables
móviles. Puede obtener la dirección de una variable móvil si la "fija" o "ancla" con una
instrucción fixed. La dirección obtenida solo es válida dentro del bloque de una
instrucción fixed . En el ejemplo siguiente se muestra cómo usar una instrucción fixed
y el operador & :
C#
unsafe
byte[] bytes = { 1, 2, 3 };
Para obtener más información sobre las variables fijas y móviles, vea la sección Variables
fijas y móviles de Especificación del lenguaje C#.
El operador binario & calcula el AND lógico de sus operandos booleanos o el AND
lógico bit a bit de sus operandos enteros.
C#
unsafe
*pointerToLetter = 'Z';
C#
x->y
es equivalente a
C#
(*x).y
public int X;
public int Y;
Coords coords;
Coords* p = &coords;
p->X = 3;
p->Y = 4;
C#
unsafe
pointerToChars[i] = (char)i;
Console.Write(pointerToChars[i]);
// Output:
7 Nota
No puede usar [] para el acceso de elemento de puntero con una expresión de tipo
void* .
Para obtener información sobre las operaciones aritméticas admitidas con tipos
numéricos, vea Operadores aritméticos.
unsafe
Resta de puntero
En el caso de dos punteros p1 y p2 de tipo T* , la expresión p1 - p2 genera la
diferencia entre las direcciones proporcionadas por p1 y p2 dividida por sizeof(T) . El
tipo del resultado es long . Es decir, p1 - p2 se calcula como ((long)(p1) - (long)(p2))
/ sizeof(T) .
C#
unsafe
int* p1 = &numbers[1];
int* p2 = &numbers[5];
Ambos operadores se admiten con dos formatos: postfijo ( p++ y p-- ) y prefijo ( ++p y -
-p ). El resultado de p++ y p-- es el valor de p antes de la operación. El resultado de ++p
y --p es el valor de p después de la operación.
C#
unsafe
int* p1 = &numbers[0];
int* p2 = p1;
// Output is similar to
Prioridad de operadores
En la lista siguiente se ordenan los operadores relacionados con el puntero desde la
prioridad más alta a la más baja:
Vea también
Referencia de C#
Operadores y expresiones de C#
Código no seguro, tipos de puntero y punteros de función
unsafe (palabra clave)
Instrucción fixed
Expresión stackalloc
sizeof (operador)
Operadores de asignación (referencia de
C#)
Artículo • 17/02/2023 • Tiempo de lectura: 3 minutos
C#
a = b = c
se evalúa como
C#
a = (b = c)
En el ejemplo siguiente se muestra el uso del operador de asignación con una variable
local, una propiedad y un elemento indexador como su operando izquierdo:
C#
Console.WriteLine(numbers.Capacity);
numbers.Capacity = 100;
Console.WriteLine(numbers.Capacity);
// Output:
// 4
// 100
int newFirstElement;
newFirstElement = 5;
numbers[0] = newFirstElement;
Console.WriteLine(originalFirstElement);
Console.WriteLine(numbers[0]);
// Output:
// 1
// 5
asignación de referencia
La asignación de referencia = ref convierte su operando de la izquierda en un alias en el
operando de la derecha. El operando de la izquierda debe ser un valor ref local, ref
readonly local o un ref campo de ref struct . Los operandos deben ser del mismo tipo.
C#
Display(arr);
arrayElement = 3.0;
Display(arr);
arrayElement = 5.0;
Display(arr);
// Output:
// 0 0 0
// 3 0 0
// 3 0 5
En el ejemplo anterior, la variable ref local arrayElement se inicializa como un alias para
el primer elemento de la matriz. A continuación, se reasigna para convertirse en un alias
para el último elemento de la matriz. Como es un alias, al actualizar su valor con un
operador = de asignación normal, también se actualiza el elemento de la matriz
correspondiente.
Asignación compuesta
Para un operador binario op , una expresión de asignación compuesta con el formato
C#
x op= y
es equivalente a
C#
x = x op y
Vea también
Referencia de C#
Operadores y expresiones de C#
ref (palabra clave)
Uso de la asignación compuesta (reglas de estilo IDE0054 e IDE0074)
Expresiones lambda y funciones
anónimas
Artículo • 09/03/2023 • Tiempo de lectura: 16 minutos
Use una expresión lambda para crear una función anónima. Use el operador de
declaración lambda => para separar la lista de parámetros de la lamba de su cuerpo.
Una expresión lambda puede tener cualquiera de las dos formas siguientes:
C#
C#
Para crear una expresión lambda, especifique los parámetros de entrada (si existen) a la
izquierda del operador lambda y una expresión o bloque de instrucciones en el otro
lado.
Toda expresión lambda se puede convertir en un tipo delegado. El tipo delegado al que
se puede convertir una expresión lambda se define según los tipos de sus parámetros y
el valor devuelto. Si una expresión lambda no devuelve un valor, se puede convertir en
uno de los tipos delegados Action ; de lo contrario, se puede convertir en uno de los
tipos delegados Func . Por ejemplo, una expresión lambda que tiene dos parámetros y
no devuelve ningún valor corresponde a un delegado Action<T1,T2>. Una expresión
lambda que tiene un parámetro y devuelve un valor se puede convertir en un delegado
Func<T,TResult>. En el ejemplo siguiente, la expresión lambda x => x * x , que
especifica un parámetro denominado x y devuelve el valor de x al cuadrado, se asigna
a una variable de un tipo delegado:
C#
Console.WriteLine(square(5));
// Output:
// 25
Las expresiones lambda también se pueden convertir en los tipos de árbol de expresión,
como se muestra en los ejemplos siguientes:
C#
Console.WriteLine(e);
// Output:
// x => (x * x)
Puede usar expresiones lambda en cualquier código que requiera instancias de tipos
delegados o de árboles de expresión, por ejemplo, como un argumento del método
Task.Run(Action) para pasar el código que se debe ejecutar en segundo plano. También
puede usar expresiones lambda al escribir LINQ en C#, como se muestra en el ejemplo
siguiente:
C#
int[] numbers = { 2, 3, 4, 5 };
// Output:
// 4 9 16 25
Lambdas de expresión
Una expresión lambda con una expresión en el lado derecho del operador => se
denomina lambda de expresión. Una expresión lambda devuelve el resultado de evaluar
la condición y tiene la siguiente forma:
C#
El cuerpo de una expresión lambda puede constar de una llamada al método. Pero si
crea árboles de expresión que se evalúan fuera del contexto de Common Language
Runtime (CLR) de .NET, como en SQL Server, no debe usar llamadas de métodos en
expresiones lambda. Los métodos no tendrán ningún significado fuera del contexto de
Common Language Runtime (CLR) de .NET.
Lambdas de instrucción
Una lambda de instrucción es similar a un lambda de expresión, salvo que las
instrucciones se encierran entre llaves:
C#
C#
Console.WriteLine(greeting);
};
greet("World");
// Output:
// Hello World!
C#
Si una expresión lambda solo tiene un parámetro de entrada, los paréntesis son
opcionales:
C#
C#
C#
Los tipos de parámetro de entrada deben ser todos explícitos o todos implícitos; de lo
contrario, se produce un error del compilador CS0748.
A partir de C# 9.0, puede usar descartes para especificar dos o más parámetros de
entrada de una expresión lambda que no se usan en la expresión:
C#
Los parámetros de descarte lambda pueden ser útiles cuando se usa una expresión
lambda para proporcionar un controlador de eventos.
7 Nota
Lambdas asincrónicas
Puede crear fácilmente expresiones e instrucciones lambda que incorporen el
procesamiento asincrónico mediante las palabras clave async y await . Por ejemplo, en el
siguiente ejemplo de formularios Windows Forms se incluye un controlador de eventos
que llama y espera un método asincrónico, ExampleMethodAsync .
C#
public Form1()
InitializeComponent();
button1.Click += button1_Click;
await ExampleMethodAsync();
await Task.Delay(1000);
Puede agregar el mismo controlador de eventos utilizando una lambda asincrónica. Para
agregar este controlador, agregue un modificador async antes de la lista de parámetros
lambda, como se muestra en el ejemplo siguiente:
C#
public Form1()
InitializeComponent();
await ExampleMethodAsync();
};
await Task.Delay(1000);
Para obtener más información sobre cómo crear y usar métodos asincrónicos, vea
Programación asincrónica con async y await.
Para definir una tupla, incluya entre paréntesis una lista delimitada por comas de los
componentes. En el ejemplo siguiente se usa la tupla con tres componentes para pasar
una secuencia de números a una expresión lambda, que duplica cada valor y devuelve
una tupla con tres componentes que contiene el resultado de las multiplicaciones.
C#
// Output:
Normalmente, los campos de una tupla se denominan Item1 , Item2 , etc. aunque puede
definir una tupla con componentes con nombre, como en el ejemplo siguiente.
C#
Func<(int n1, int n2, int n3), (int, int, int)> doubleThem = ns => (2 *
ns.n1, 2 * ns.n2, 2 * ns.n3);
Para más información sobre las tuplas de C#, consulte el artículo sobre los tipos de
tuplas.
Lambdas con los operadores de consulta
estándar
LINQ to Objects, entre otras implementaciones, tiene un parámetro de entrada cuyo
tipo es uno de la familia Func<TResult> de delegados genéricos. Estos delegados usan
parámetros de tipo para definir el número y el tipo de los parámetros de entrada, así
como el tipo de valor devuelto del delegado. Los delegados Func son útiles para
encapsular expresiones definidas por el usuario que se aplican a cada elemento en un
conjunto de datos de origen. Por ejemplo, considere el tipo delegado Func<T,TResult>:
C#
Se pueden crear instancias del delegado como una instancia Func<int, bool> , donde
int es un parámetro de entrada y bool es el valor devuelto. El valor devuelto siempre
C#
Console.WriteLine(result); // False
C#
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);
El siguiente ejemplo genera una secuencia que contiene todos los elementos de la
matriz numbers que preceden al 9, ya que ese es el primer número de la secuencia que
no cumple la condición:
C#
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var firstNumbersLessThanSix = numbers.TakeWhile(n => n < 6);
// Output:
// 5 4 1 3
C#
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);
// Output:
// 5 4
C#
new[] { 1, 2, 3, 4, 5 },
new[] { 0, 0, 0 },
new[] { 9, 8 },
new[] { 1, 0, 1, 0, 1, 0, 1, 0 }
};
var setsWithManyPositives =
select numberSet;
// Output:
// 1 2 3 4 5
// 1 0 1 0 1 0 1 0
C#
Las reglas generales para la inferencia de tipos de las lambdas son las siguientes:
A partir de C# 10, una expresión lambda puede tener un tipo natural. En lugar de
forzarle a declarar un tipo de delegado, como Func<...> o Action<...> para una
expresión lambda, el compilador puede deducir el tipo de delegado de la expresión
lambda. Por ejemplo, consideremos la siguiente declaración:
C#
C#
Los grupos de métodos (es decir, los nombres de método sin listas de parámetros) con
exactamente una sobrecarga tienen un tipo natural:
C#
C#
C#
var parse = s => int.Parse(s); // ERROR: Not enough type info in the lambda
C#
C#
var choose = (bool b) => b ? 1 : "two"; // ERROR: Can't infer return type
A partir de C# 10, puede especificar el tipo de valor devuelto de una expresión lambda
antes de los parámetros de entrada. Al especificar un tipo de valor devuelto explícito,
debe encuadrar entre paréntesis los parámetros de entrada:
C#
Atributos
A partir de C# 10, puede agregar atributos a una expresión lambda y sus parámetros. En
el ejemplo siguiente se muestra cómo agregar atributos a una expresión lambda:
C#
También puede agregar atributos a los parámetros de entrada o al valor devuelto, como
se muestra en el ejemplo siguiente:
C#
Como se muestra en los ejemplos anteriores, debe encuadrar entre paréntesis los
parámetros de entrada al agregar atributos a una expresión lambda o a sus parámetros.
) Importante
C#
int j = 0;
updateCapturedLocalVariable = x =>
j = x;
};
isEqualToCapturedLocalVariable = x => x == j;
updateCapturedLocalVariable(10);
int gameInput = 5;
game.Run(gameInput);
int anotherJ = 3;
game.updateCapturedLocalVariable!(anotherJ);
// Output:
Las reglas siguientes se aplican al ámbito de las variables en las expresiones lambda:
A partir de C# 9.0, puede aplicar el modificador static a una expresión lambda para
evitar la captura involuntaria de variables locales o el estado de la instancia por parte de
la expresión lambda:
C#
Vea también
Uso de la función local en lugar de lambda (regla de estilo IDE0039)
Referencia de C#
Operadores y expresiones de C#
LINQ (Language Integrated Query)
Árboles de expresión
Funciones locales frente a expresiones lambda
Consultas de ejemplo de LINQ
Ejemplo de XQuery
101 ejemplos de LINQ
Coincidencia de patrones: las is
expresiones y switch , y los operadores
and , or y not en patrones
Artículo • 15/02/2023 • Tiempo de lectura: 19 minutos
La expresión se usais, la instrucción switch y la expresión switch para que coincida con
una expresión de entrada con cualquier número de características. C# admite varios
patrones, como declaración, tipo, constante, relacional, propiedad, lista, var y descarte.
Los patrones se pueden combinar mediante palabras clave lógica booleanas and , or y
not .
Expresión is
switch (Instrucción)
Expresión switch
En esas construcciones, puede hacer coincidir una expresión de entrada con cualquiera
de los siguientes patrones:
Para obtener el ejemplo de cómo usar esos patrones para compilar un algoritmo basado
en datos, vea Tutorial: Uso de la coincidencia de patrones para compilar algoritmos
basados en tipos y basados en datos.
C#
Un patrón de declaración con el tipo T coincide con una expresión cuando el resultado
de una expresión no es NULL y se cumple cualquiera de las condiciones siguientes:
El tipo en tiempo de ejecución del resultado de una expresión deriva del tipo T ,
implementa una interfaz T , o bien otra conversión de referencia implícita existe en
T . En el ejemplo siguiente se muestran dos casos en los que esta condición es
verdadera:
C#
Console.WriteLine(GetSourceLabel(numbers)); // output: 1
Console.WriteLine(GetSourceLabel(letters)); // output: 2
_ => 3,
};
Existe una conversión boxing o unboxing del tipo en tiempo de ejecución del
resultado de una expresión al tipo T .
C#
int? xNullable = 7;
int y = 23;
object yBoxed = y;
Si solo desea comprobar el tipo de una expresión, puede usar un patrón de descarte _
en lugar del nombre de una variable, como se muestra en el ejemplo siguiente:
C#
};
A partir de C# 9.0, para ese propósito se puede usar un patrón de tipo, como se muestra
en el ejemplo siguiente:
C#
};
Al igual que un patrón de declaración, un patrón de tipo coincide con una expresión
cuando el resultado de una expresión no es NULL y su tipo en tiempo de ejecución
cumple cualquiera de las condiciones mencionadas anteriormente.
Para comprobar si no es NULL, puede usar un patrón de constante null negada, como
se muestra en el ejemplo siguiente:
C#
// ...
Para obtener más información, vea las secciones Patrón de declaración y Patrón de tipo
de las notas de propuesta de características.
Patrón de constante
Utilice un patrón de constante para probar si el resultado de una expresión es igual a
una constante especificada, como se muestra en el ejemplo siguiente:
C#
1 => 12.0m,
2 => 20.0m,
3 => 27.0m,
4 => 32.0m,
0 => 0.0m,
};
La expresión debe ser un tipo que se puede convertir al tipo constante, con una
excepción: una expresión cuyo tipo es Span<char> o ReadOnlySpan<char> se puede
comparar con cadenas constantes en C# 11 y versiones posteriores.
C#
if (input is null)
return;
A partir de C# 9.0, se puede usar un patrón de constante negado null para comprobar
las que no son null, como se muestra en el ejemplo siguiente:
C#
// ...
Patrones relacionales
A partir de C# 9.0, se usa un patrón relacional para comparar el resultado de una
expresión con una constante, como se muestra en el ejemplo siguiente:
C#
_ => "Acceptable",
};
En un patrón relacional, se puede usar cualquiera de los operadores relacionales < , > ,
<= o >= . La parte derecha de un patrón relacional debe ser una expresión constante. La
C#
};
Patrones lógicos
A partir de C# 9.0, se usan los combinadores de patrones not , and y or para crear los
siguientes patrones lógicos:
Patrón de negación not que coincide con una expresión cuando el patrón negado
no coincide con ella. En el ejemplo siguiente se muestra cómo se puede negar un
patrón constante null para comprobar si una expresión no es null:
C#
// ...
Patrón conjuntivo and que coincide con una expresión cuando ambos patrones
coinciden con ella. En el ejemplo siguiente se muestra cómo se pueden combinar
patrones relacionales para comprobar si un valor se encuentra en un intervalo
determinado:
C#
};
Patrón disyuntivo or que coincide con una expresión cuando uno de los patrones
coincide con ella, como se muestra en el ejemplo siguiente:
C#
3 or 4 or 5 => "spring",
6 or 7 or 8 => "summer",
9 or 10 or 11 => "autumn",
12 or 1 or 2 => "winter",
};
not
and
or
C#
static bool IsLetter(char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <=
'Z');
7 Nota
El orden en el que se comprueban los patrones es indefinido. En tiempo de
ejecución, se pueden comprobar primero los patrones anidados del lado derecho
de los patrones or y and .
Patrón de propiedad
Utilice un patrón de propiedad para hacer coincidir las propiedades o los campos de una
expresión con los patrones anidados, como se muestra en el ejemplo siguiente:
C#
C#
string s => s,
};
C#
C#
Sugerencia
Patrón posicional
Utilice un patrón posicional para deconstruir el resultado de una expresión y hacer
coincidir los valores resultantes con los patrones anidados correspondientes, como se
muestra en el ejemplo siguiente:
C#
public readonly struct Point
public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
};
C#
_ => 0.0m,
};
C#
var numbers = new List<int> { 1, 2, 3 };
int sum = 0;
int count = 0;
sum += number;
count++;
C#
_ => string.Empty,
};
C#
C#
// ..
Para obtener más información, vea la sección Patrón posicional de la nota de propuesta
de características.
Patrón var
Utilice un patrón var para buscar coincidencias con cualquier expresión, incluida null , y
asignar el resultado a una nueva variable local, como se muestra en el ejemplo
siguiente:
C#
return Enumerable
.Range(start: 0, count: 5)
.ToArray();
Un patrón var es útil cuando se necesita una variable temporal dentro de una expresión
booleana para contener el resultado de los cálculos intermedios. También puede usar un
var patrón cuando necesite realizar más comprobaciones en when caso de que se
};
Para obtener más información, vea la sección Patrón Var de la nota de propuesta de
características.
Patrón de descarte
Utilice un patrón de descarte _ para buscar coincidencias con cualquier expresión,
incluida null , como se muestra en el ejemplo siguiente:
C#
_ => 0.0m,
};
C#
return;
Patrones de lista
A partir de C# 11, puede buscar coincidencias de una matriz o una lista con una
secuencia de patrones, como se muestra en el ejemplo siguiente:
C#
int[] numbers = { 1, 2, 3 };
Como se muestra en el ejemplo anterior, un patrón de lista coincide cuando cada patrón
anidado coincide con el elemento correspondiente de una secuencia de entrada. Puede
usar cualquier patrón dentro de un patrón de lista. Para buscar coincidencias con
cualquier elemento, use el patrón de descarte o, si también desea capturar el elemento,
el patrón var, como se muestra en el ejemplo siguiente:
C#
// Output:
C#
Un patrón de segmento busca coincidencias con cero o más elementos. Puede usar
como máximo un patrón de segmento en un patrón de lista. El patrón de segmento solo
puede aparecer en un patrón de lista.
Console.WriteLine(result);
Console.WriteLine(result);
Vea también
Referencia de C#
Operadores y expresiones de C#
Información general sobre la coincidencia de patrones
Tutorial: Uso de la coincidencia de patrones para compilar algoritmos basados en
tipos y basados en datos
Operadores de suma: + y +=
Artículo • 17/02/2023 • Tiempo de lectura: 2 minutos
Los operadores + y += son compatibles con los tipos numéricos enteros y de punto
flotante, el tipo string y los tipos delegados.
Para obtener información acerca del operador aritmético + , consulte las secciones
correspondientes a los operadores unarios más y menos y al operador de suma + del
artículo Operadores aritméticos.
Concatenación de cadenas
Cuando uno o ambos operandos son de tipo string, el operador + concatena las
representaciones de cadena de sus operandos (la representación de cadena de null es
una cadena vacía):
C#
// Output:
// Forgotwhite space
// Nothing to add.
La interpolación de cadenas proporciona una manera más práctica de dar formato a las
cadenas:
C#
// Output:
C#
Action ab = a + b;
ab(); // output: ab
C#
x += y
es equivalente a
C#
x = x + y
C#
int i = 5;
i += 9;
Console.WriteLine(i);
// Output: 14
story += "End.";
Console.WriteLine(story);
printer(); // output: a
Console.WriteLine();
printer(); // output: ab
Vea también
Referencia de C#
Operadores y expresiones de C#
Concatenación de varias cadenas
Eventos
Operadores aritméticos
Operadores - y -=
- y -= operadores - resta (menos)
Artículo • 17/02/2023 • Tiempo de lectura: 3 minutos
Los operadores - y -= son compatibles con los tipos numéricos enteros y de punto
flotante, y los tipos delegados.
Eliminación de delegados
Para los operandos del mismo tipo delegado, el operador - devuelve una instancia de
delegado que se calcula de la siguiente manera:
C#
var abbaab = a + b + b + a + a + b;
Console.WriteLine();
var ab = a + b;
Console.WriteLine();
C#
var abbaab = a + b + b + a + a + b;
var aba = a + b + a;
Console.WriteLine();
changed(); // output: ab
Console.WriteLine();
Console.WriteLine();
C#
a(); // output: a
Console.WriteLine();
C#
x -= y
es equivalente a
C#
x = x - y
C#
int i = 5;
i -= 9;
Console.WriteLine(i);
// Output: -4
var printer = a + b + a;
Console.WriteLine();
printer -= a;
printer(); // output: ab
Vea también
Referencia de C#
Operadores y expresiones de C#
Eventos
Operadores aritméticos
Operadores + y +=
Operador ?: operador: operador
condicional ternario
Artículo • 17/02/2023 • Tiempo de lectura: 3 minutos
C#
C#
consequent o alternative .
C#
C#
C#
a ? b : c ? d : e
se evalúa como
C#
a ? b : (c ? d : e)
Sugerencia
text
C#
Como sucede con el operador condicional original, una expresión condicional ref evalúa
solo una de las dos expresiones, ya sea consequent o alternative .
En una expresión condicional ref, los tipos de consequent y alternative deben coincidir.
Las expresiones condicionales ref no tienen tipo de destino.
C#
int index = 7;
index = 2;
// Output:
// 1 2 100 4 5
// 10 20 0 40 50
C#
string classify;
if (input >= 0)
classify = "nonnegative";
else
classify = "negative";
Vea también
Simplificación de la expresión condicional (regla de estilo IDE0075)
Referencia de C#
Operadores y expresiones de C#
if (Instrucción)
?. Operadores and ?[]
?? Operadores and ??=
ref (palabra clave)
! (permite valores NULL) (referencia de
C#)
Artículo • 17/02/2023 • Tiempo de lectura: 2 minutos
Para obtener más información sobre la característica de tipos de referencia que admiten
un valor NULL, consulte Tipos de referencia que admiten un valor NULL.
Ejemplos
Uno de los casos de uso del operador que permite un valor NULL es probar la lógica de
validación de argumentos. Por ejemplo, considere la siguiente clase:
C#
#nullable enable
Con el marco de pruebas MSTest puede crear la prueba siguiente para la lógica de
validación en el constructor:
C#
[TestMethod, ExpectedException(typeof(ArgumentNullException))]
También puede usar el operador que permite valores NULL cuando sepa a ciencia cierta
que una expresión no puede ser null , pero el compilador no consigue reconocerlo. En
el ejemplo siguiente, si el método IsValid devuelve true , su argumento no es null y
puede desreferenciarlo de forma segura:
C#
Person? p = Find("John");
if (IsValid(p))
Console.WriteLine($"Found {p!.Name}");
C#
Person? p = Find("John");
if (IsValid(p))
Console.WriteLine($"Found {p.Name}");
Vea también
Eliminación del operador de supresión innecesario (regla de estilo IDE0080)
Referencia de C#
Operadores y expresiones de C#
Tutorial: Diseño con tipos de referencia que admiten un valor NULL
?? Operadores ?? y ??=: los operadores
de fusión de NULL
Artículo • 18/02/2023 • Tiempo de lectura: 3 minutos
C#
int? a = null;
Console.WriteLine(a); // output: 0
El operando izquierdo del operador ??= debe ser una variable, una propiedad o un
elemento de indizador.
El tipo del operando izquierdo de los operadores ?? y ??= no puede ser un tipo de
valor que no acepta valores NULL. En concreto, puede usar los operadores de fusión de
NULL con parámetros de tipo sin restricciones:
C#
Console.WriteLine(a ?? backup);
Los operadores de uso combinado de NULL son asociativos a la derecha. Es decir, las
expresiones del formulario
C#
a ?? b ?? c
d ??= e ??= f
se evalúan como
C#
a ?? (b ?? c)
d ??= (e ??= f)
Ejemplos
Los operadores ?? y ??= puede resultar útiles en los siguientes escenarios:
C#
Cuando trabaja con tipos de valor que aceptan valores NULL y necesita
proporcionar un valor de un tipo de valor subyacente, use el operador ?? para
especificar el valor para proporcionar en caso de que un valor de tipo que acepta
valores NULL sea null :
C#
int? a = null;
int b = a ?? -1;
Console.WriteLine(b); // output: -1
Puede usar una expresión throw como el operando derecho del operador ?? para
hacer el código de comprobación de argumentos más conciso:
C#
El ejemplo anterior también muestra cómo usar miembros con forma de expresión
para definir una propiedad.
C#
if (variable is null)
variable = expression;
C#
Vea también
Uso de expresiones de fusión (reglas de estilo IDE0029 e IDE0030)
Referencia de C#
Operadores y expresiones de C#
?. y ?[]
Operador ?:
" se usa para definir una expresión lambda en C# | Microsoft Learn" />
El token => se admite de dos formas: como el operador lambda y como un separador
de un nombre de miembro y la implementación del miembro en una definición de
cuerpo de expresión.
Operador{1}{2}lambda
En las expresiones lambda, el operador => {4}lambda {5} separa los parámetros de
entrada del lado izquierdo y el cuerpo lambda del lado derecho.
C#
Console.WriteLine(minimalLength); // output: 5
int[] numbers = { 4, 7, 10 };
C#
int[] numbers = { 4, 7, 10 };
En el ejemplo siguiente se muestra cómo definir una expresión lambda sin parámetros
de entrada:
C#
Console.WriteLine(greet());
C#
expression debe ser una expresión de instrucción. Dado que el resultado de la expresión
se descarta, el tipo de valor devuelto de esa expresión puede ser cualquiera.
C#
C#
public override string ToString()
Vea también
Referencia de C#
Operadores y expresiones de C#
:: operador : el operador de alias del
espacio de nombres
Artículo • 17/02/2023 • Tiempo de lectura: 2 minutos
C#
Un alias externo.
C#
namespace MyCompany.MyProduct.System
class Program
class Console
7 Nota
Vea también
Referencia de C#
Operadores y expresiones de C#
Operador await: espera
asincrónicamente para que se complete
una tarea
Artículo • 18/02/2023 • Tiempo de lectura: 3 minutos
El operador await suspende la evaluación del método async envolvente hasta que se
completa la operación asincrónica representada por su operando. Cuando se completa
la operación asincrónica, el operador await devuelve el resultado de la operación, si
existe. Cuando el operador await se aplica al operando que representa una operación
ya completada, devuelve el resultado de la operación inmediatamente sin suspender el
método envolvente. El operador await no bloquea el subproceso que evalúa el método
async. Cuando el operador await suspende el método async envolvente, el control
vuelve al autor de la llamada del método.
C#
using System;
using System.Net.Http;
using System.Threading.Tasks;
Console.WriteLine($"{nameof(DownloadDocsMainPageAsync)}: About to
start downloading.");
Console.WriteLine($"{nameof(DownloadDocsMainPageAsync)}: Finished
downloading.");
return content.Length;
7 Nota
El operando del operador await suele ser de uno de los siguientes tipos de .NET: Task,
Task<TResult>, ValueTask o ValueTask<TResult>. Aunque cualquier expresión con await
puede ser el operando del operador await . Para obtener más información, vea la
sección Expresiones con await de la especificación del lenguaje C#.
Se usa la instrucción await using para trabajar con un objeto descartable de forma
asincrónica, es decir, un objeto de un tipo que implementa una interfaz
IAsyncDisposable. Para obtener más información, vea la sección Uso de la eliminación
asincrónica del artículo Implementación de un método DisposeAsync.
Vea también
Referencia de C#
Operadores y expresiones de C#
async
Modelo de programación asincrónica de tareas
Programación asincrónica
Tutorial: Acceso a la web con async y await
Tutorial: generación y consumo de secuencias asincrónicas
expresiones de valor predeterminado:
genera el valor predeterminado
Artículo • 17/02/2023 • Tiempo de lectura: 2 minutos
operador default
El argumento del operador default debe ser el nombre de un tipo o un parámetro de
tipo, como se muestra en el ejemplo siguiente:
C#
Console.WriteLine(default(int)); // output: 0
void DisplayDefaultOf<T>()
DisplayDefaultOf<int?>();
DisplayDefaultOf<System.Numerics.Complex>();
DisplayDefaultOf<System.Collections.Generic.List<int>>();
// Output:
Literal default
Puede usar el literal default para generar el valor predeterminado de un tipo cuando el
compilador puede deducir el tipo de expresión. La expresión literal default genera el
mismo valor que la expresión default(T) cuando se deduce el tipo T . Puede usar el
literal default en cualquiera de los casos siguientes:
En la asignación o inicialización de una variable.
En la declaración del valor predeterminado de un parámetro de método opcional.
En una llamada al método para proporcionar un valor de argumento.
En una instrucción return o como una expresión de un miembro con cuerpo de
expresión.
C#
if (length < 0)
array[i] = initialValue;
return array;
Display(InitializeArray<int>(3)); // output: [ 0, 0, 0 ]
Sugerencia
Use la regla de estilo de .NET IDE0034 para especificar una preferencia sobre el uso
del literal default en el código base.
C#
7 Nota
Las expresiones lambda proporcionan una manera más concisa y expresiva de crear
una función anónima. Use el operador=> para construir una expresión lambda:
C#
Para más información sobre las características de las expresiones lambda, como
capturar las variables externas, consulte Expresiones lambda.
C#
greet();
introduce(42, 2.7);
// Output:
// Hello!
// This is world!
Esta es la única funcionalidad de los métodos anónimos que las expresiones lambda no
admiten. En todos los demás casos, una expresión lambda es una de las maneras
preferidas de escribir código alineado.
A partir de C# 9.0, puede usar descartes para especificar dos o más parámetros de
entrada de un método anónimo que no usa el método:
C#
C#
También puede usar la palabra clave delegate para declarar un tipo delegado.
C#
C#
Action a = StaticFunction;
Antes de C# 11, se tenía que usar una expresión lambda para reutilizar un solo objeto
delegado:
C#
Vea también
Referencia de C#
Operadores y expresiones de C#
Operador=>
Operador is (Referencia de C#)
Artículo • 17/02/2023 • Tiempo de lectura: 2 minutos
C#
C#
int i = 34;
object iBoxed = i;
C#
if (input is null)
return;
C#
Console.WriteLine(result.ToString());
A partir de C# 11, puede usar patrones de lista para buscar coincidencias con
elementos de una lista o matriz. El código siguiente comprueba las matrices de
valores enteros en las posiciones esperadas:
C#
int[] empty = { };
int[] one = { 1 };
int[] odd = { 1, 3, 5 };
int[] even = { 2, 4, 6 };
int[] fib = { 1, 1, 2, 3, 5 };
7 Nota
Vea también
Referencia de C#
Operadores y expresiones de C#
Patrones
Tutorial: Uso de la coincidencia de patrones para compilar algoritmos basados en
tipos y basados en datos
Operadores de conversión y prueba de tipos
Expresión nameof (referencia de C#)
Artículo • 17/02/2023 • Tiempo de lectura: 2 minutos
Una expresión nameof genera el nombre de una variable, un tipo o un miembro como
constante de cadena. La expresión nameof se evalúa en tiempo de compilación y no
tiene efecto en tiempo de ejecución. Cuando el operando es un tipo o un espacio de
nombres, el nombre generado no está completo. En el ejemplo siguiente se muestra el
uso de una expresión nameof :
C#
C#
var @new = 5;
Console.WriteLine(nameof(@new)); // output: new
Puede usar una expresión nameof para facilitar el mantenimiento del código de
comprobación de argumentos:
C#
A partir de C# 11, puede usar una expresión nameof con un parámetro de método
dentro de un atributo en un método o su parámetro. En el código siguiente se muestra
cómo hacerlo para un atributo en un método, una función local y el parámetro de una
expresión lambda:
C#
[ParameterString(nameof(msg))]
[ParameterString(nameof(T))]
Una expresión nameof con un parámetro es útil cuando se usan los atributos de análisis
que admiten valores NULL o el atributo CallerArgumentExpression.
Vea también
Referencia de C#
Operadores y expresiones de C#
Conversión de typeof en nameof (regla de estilo IDE0082)
operador new: el operador new crea una
nueva instancia de un tipo
Artículo • 17/02/2023 • Tiempo de lectura: 3 minutos
El operador new crea una nueva instancia de un tipo. También puede usar la palabra
clave new como un modificador de declaración de miembro o una restricción de tipo
genérico.
C#
dict["first"] = 10;
dict["second"] = 20;
dict["third"] = 30;
// Output:
Puede usar un inicializador de colección u objeto con el operador new para crear una
instancia e inicializar un objeto en una sola instrucción, como se muestra en el ejemplo
siguiente:
C#
["first"] = 10,
["second"] = 20,
["third"] = 30
};
// Output:
A partir de C# 9.0, las expresiones de invocación del constructor tienen tipo de destino.
Es decir, si se conoce el tipo de destino de una expresión, puede omitir un nombre de
tipo, como se muestra en el ejemplo siguiente:
C#
List<int> xs = new();
[1] = new() { 1, 2, 3 },
[2] = new() { 5, 8, 3 },
[5] = new() { 1, 0, 4 }
};
Si se desconoce el tipo de destino de una expresión new (por ejemplo, cuando se usa la
palabra clave var), se debe especificar un nombre de tipo.
creación de matriz
También se usa el operador new para crear una instancia de matriz, como se muestra en
el ejemplo siguiente:
C#
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;
// Output:
// 10, 20, 30
C#
var a = new int[3] { 10, 20, 30 };
C#
Console.WriteLine($"{example.Greeting}, {example.Name}!");
// Output:
// Hello, World!
Para más información sobre la expresión new con tipo de destino, consulte la nota de
propuesta de características.
Vea también
Referencia de C#
Operadores y expresiones de C#
Inicializadores de objeto y colección
operador sizeof: determine las
necesidades de memoria de un tipo
determinado
Artículo • 17/02/2023 • Tiempo de lectura: 2 minutos
El operador sizeof devuelve el número de bytes ocupados por una variable de un tipo
determinado. El argumento del operador sizeof debe ser el nombre de un tipo
administrado o un parámetro de tipo que está restringido para ser un tipo no
administrado.
sizeof(sbyte) 1
sizeof(byte) 1
sizeof(short) 2
sizeof(ushort) 2
sizeof(int) 4
sizeof(uint) 4
sizeof(long) 8
sizeof(ulong) 8
sizeof(char) 2
sizeof(float) 4
sizeof(double) 8
sizeof(decimal) 16
sizeof(bool) 1
C#
Console.WriteLine(sizeof(byte)); // output: 1
Console.WriteLine(sizeof(double)); // output: 8
unsafe
Console.WriteLine(sizeof(Point*)); // output: 8
Vea también
Referencia de C#
Operadores y expresiones de C#
Operadores relacionados con el puntero
Tipos de puntero
Tipos relacionados con el intervalo y la memoria
Elementos genéricos en .NET
Expresión stackalloc (referencia de C#)
Artículo • 17/02/2023 • Tiempo de lectura: 3 minutos
Puede asignar el resultado de una expresión stackalloc a una variable de uno de los
siguientes tipos:
C#
int length = 3;
numbers[i] = i;
Cuando se trabaja con esos tipos, puede usar una expresión stackalloc en
expresiones condicionales o de asignación, como se muestra en el ejemplo
siguiente:
C#
Se puede usar una expresión stackalloc dentro de otras expresiones siempre que
se permita una variable Span<T> o ReadOnlySpan<T>, tal como se muestra en
este ejemplo:
C#
Span<int> numbers = stackalloc[] { 1, 2, 3, 4, 5, 6 };
Console.WriteLine(ind); // output: 1
7 Nota
C#
unsafe
int length = 3;
numbers[i] = i;
En el caso de los tipos de puntero, solo se puede usar una expresión stackalloc
en una declaración de variable local para inicializar la variable.
C#
7 Nota
El contenido de la memoria recién asignada está sin definir. Debe inicializarlo antes del
uso. Por ejemplo, puede usar el método Span<T>.Clear que establece todos los
elementos en el valor predeterminado de tipo T .
C#
Seguridad
El uso de stackalloc habilita automáticamente las características de detección de
saturación del búfer en el entorno Common Language Runtime (CLR). Si se detecta
saturación del búfer, se finaliza el proceso lo antes posible para minimizar el riesgo de
que se ejecute código malintencionado.
Vea también
Referencia de C#
Operadores y expresiones de C#
Operadores relacionados con el puntero
Tipos de puntero
Tipos relacionados con el intervalo y la memoria
Qué hacer y qué no hacer de stackalloc
expresión switch: expresiones de
coincidencia de patrones mediante la
switch palabra clave
Artículo • 17/02/2023 • Tiempo de lectura: 3 minutos
Se usa la expresión switch para evaluar una expresión única a partir de una lista de
expresiones candidatas basadas en una coincidencia de patrón con una expresión de
entrada. Para obtener información sobre la instrucción switch que admite la semántica
switch en un contexto de instrucción, consulte la sección instrucción switch del artículo
Instrucciones de selección.
En el ejemplo siguiente se muestra una expresión switch , que traslada los valores de un
objeto enum que representa las direcciones visuales de un mapa en línea hasta la
dirección cardinal correspondiente:
C#
Up,
Down,
Right,
Left
North,
South,
East,
West
};
Console.WriteLine($"Cardinal orientation is
{ToOrientation(direction)}");
// Output:
) Importante
Para obtener información sobre los patrones admitidos por una expresión switch y
más ejemplos, consulte Patrones.
C#
};
Sugerencia
Para garantizar que una expresión switch controle todos los valores de entrada
posibles, proporcione un brazo de expresión switch con un patrón de descarte.
Especificación del lenguaje C#
Para obtener más información, vea la sección sobre la expresión de switch de la nota de
propuesta de características.
Vea también
Uso de la expresión "switch" (regla de estilo IDE0066)
Adición de casos que faltan a la expresión "switch" (regla de estilo IDE0072)
Referencia de C#
Operadores y expresiones de C#
Patrones
Tutorial: Uso de la coincidencia de patrones para compilar algoritmos basados en
tipos y basados en datos
Instrucción switch
Operadores true y false: trate los
objetos como un valor booleano
Artículo • 17/02/2023 • Tiempo de lectura: 2 minutos
El operador true devuelve el valor bool true para indicar que su operando es
definitivamente true. El operador false devuelve el valor bool true para indicar que su
operando es definitivamente false. Los operadores true y false no garantizan que se
complementen entre sí. Es decir, tanto el operador true como false podrían devolver
el valor bool false del mismo operando. Si un tipo define uno de los dos operadores,
también debe definir otro operador.
Sugerencia
Use el tipo bool? , si tiene que admitir la lógica de tres valores (por ejemplo,
cuando trabaja con bases de datos que admiten un tipo booleano de tres valores).
C# proporciona los operadores & y | que admiten la lógica de tres valores con los
operandos bool? . Para más información, consulte la sección Operadores lógicos
booleanos que aceptan valores NULL del artículo Operadores lógicos booleanos.
Expresiones booleanas
Un tipo con el operador true definido puede ser el tipo de un resultado de una
expresión condicional de control en las instruciones if, do, while y for y en el operador
condicional ?:. Para más información, vea la sección Expresiones booleanas de la
Especificación del lenguaje C#.
C#
this.status = status;
return Red;
if (x == Yellow || y == Yellow)
return Yellow;
return Green;
public override bool Equals(object obj) => obj is LaunchStatus other &&
this == other;
return LaunchStatus.Red;
return LaunchStatus.Yellow;
Consola
Wait!
Vea también
Referencia de C#
Operadores y expresiones de C#
Expresión with: la mutación no
destructiva crea un objeto nuevo con
propiedades modificadas
Artículo • 18/02/2023 • Tiempo de lectura: 3 minutos
Disponible en C# 9.0 y versiones posteriores, se trata de una expresión with que genera
una copia de su operando con las propiedades y los campos especificados modificados.
se usa la sintaxis del inicializador de objeto para especificar qué miembros se van a
modificar y sus nuevos valores:
C#
using System;
var p3 = p1 with
Name = "C",
Y = 4
};
El resultado de una expresión with tiene el mismo tipo de entorno de ejecución que el
operando de la expresión, como se muestra en el ejemplo siguiente:
C#
using System;
Point p2 = p1 with { X = 5, Y = 3 };
C#
using System;
using System.Collections.Generic;
original.Tags.Add("C");
C#
using System;
using System.Collections.Generic;
Number = original.Number;
original.Tags.Add("C");
Expresión with
Copiar y clonar miembros
Vea también
Referencia de C#
Operadores y expresiones de C#
Registros
Tipos de estructura
Sobrecarga de operadores: operadores
unarios, aritméticos, de igualdad y de
comparación predefinidos
Artículo • 18/02/2023 • Tiempo de lectura: 3 minutos
Use la palabra clave operator para declarar un operador. Una declaración de operador
debe cumplir las reglas siguientes:
C#
if (denominator == 0)
num = numerator;
den = denominator;
=> a + (-b);
if (b.num == 0)
{
Console.WriteLine(-a); // output: -5 / 4
esos dos tipos. Es decir, sería posible agregar un valor entero a una fracción y obtener
como resultado una fracción.
También usa la palabra clave operator para definir una conversión de tipos
personalizada. Para obtener más información, vea Operadores de conversión definidos
por el usuario.
Operadores sobrecargables
En la tabla siguiente se muestran los operadores que se pueden sobrecargar:
Operadores Notas
+x, -x, !x, ~x, ++, --, true, false Los operadores true y false deben sobrecargarse juntos.
Operadores Notas
x + y, x - y, x * y, x / y, x % y,
x & y, x | y, x ^ y,
Operadores no sobrecargables
En la tabla siguiente se muestran los operadores que no se pueden sobrecargar:
Operadores Alternativas
x && y, x || y Sobrecargue los operadores true y false, así como los operadores &
o |. Para obtener más información, vea Operadores lógicos
condicionales definidos por el usuario.
+=, -=, *=, /=, %=, &=, |=, Sobrecargue el operador binario correspondiente. Por ejemplo,
^=, <<=, >>=, >>>= cuando sobrecarga el operador + binario, += se sobrecarga
implícitamente.
Sobrecarga de operadores
Operadores
Vea también
Referencia de C#
Operadores y expresiones de C#
Operadores de conversión definidos por el usuario
Directrices de diseño: sobrecargas de operador
Directrices de diseño: operadores de igualdad
Why are overloaded operators always static in C#? (¿Por qué los operadores
sobrecargados son siempre estáticos en C#?)
Instrucciones de iteración: for , foreach ,
do y while
Artículo • 19/02/2023 • Tiempo de lectura: 7 minutos
En cualquier punto del cuerpo de una instrucción de iteración, se puede salir del bucle
mediante la instrucción break. Puede avanzar a la próxima iteración en el bucle
mediante la instrucción continue.
Instrucción for
La instrucción for ejecuta una instrucción o un bloque de instrucciones mientras una
expresión booleana especificada se evalúa como true . En el ejemplo siguiente se
muestra la instrucción for , que ejecuta su cuerpo mientras que un contador entero sea
menor que tres:
C#
Console.Write(i);
// Output:
// 012
La sección inicializador, que se ejecuta solo una vez, antes de entrar en el bucle.
Normalmente, se declara e inicializa una variable de bucle local en esa sección. No
se puede acceder a la variable declarada desde fuera de la instrucción for .
C#
int i = 0
C#
i < 3
La sección iterador, que define lo que sucede después de cada iteración del cuerpo
del bucle.
C#
i++
Si no declara una variable de bucle en la sección inicializador, también puede usar cero
o varias de las expresiones de la lista anterior de dicha sección. En el ejemplo siguiente
se muestran varios usos menos comunes de las secciones inicializador e iterador:
asignar un valor a una variable externa en la sección inicializador, invocar un método en
las secciones inicializador e iterador, y cambiar los valores de dos variables en la sección
iterador:
C#
int i;
int j = 3;
//...
// Output:
C#
for ( ; ; )
//...
Instrucción foreach
La instrucción foreach ejecuta una instrucción o un bloque de instrucciones para cada
elemento de una instancia del tipo que implementa la interfaz
System.Collections.IEnumerable o System.Collections.Generic.IEnumerable<T>, como se
muestra en el siguiente ejemplo:
C#
Console.Write($"{element} ");
// Output:
// 0 1 1 2 3 5 8 13
La instrucción foreach no está limitada a esos tipos. Puede usarla con una instancia de
cualquier tipo que cumpla las condiciones siguientes:
En el siguiente ejemplo se usa la instrucción foreach con una instancia del tipo
System.Span<T>, que no implementa ninguna interfaz:
C#
Console.Write($"{number} ");
// Output:
// 3 14 15 92 6
C#
int num = 0;
item = num++;
Console.Write($"{item} ");
// Output:
// 0 1 2 3 4 5 6 7 8 9
await foreach
Puede usar la instrucción await foreach para consumir un flujo asincrónico de datos, es
decir, el tipo de colección que implementa la interfaz IAsyncEnumerable<T>. Cada
iteración del bucle se puede suspender mientras el siguiente elemento se recupera de
forma asincrónica. En el ejemplo siguiente se muestra cómo usar la instrucción await
foreach :
C#
Console.WriteLine(item);
También puede usar la instrucción await foreach con una instancia de cualquier tipo
que cumpla las condiciones siguientes:
C#
También puede especificar de forma explícita el tipo de una variable de iteración, como
se muestra en el código siguiente:
C#
IEnumerable<T> collection = new T[5];
Instrucción do
La instrucción do ejecuta una instrucción o un bloque de instrucciones mientras que
una expresión booleana especificada se evalúa como true . Como esa expresión se
evalúa después de cada ejecución del bucle, un bucle do se ejecuta una o varias veces.
La instrucción do es diferente de un bucle while, que se ejecuta cero o varias veces.
C#
int n = 0;
do
Console.Write(n);
n++;
// Output:
// 01234
Instrucción while
La instrucción while ejecuta una instrucción o un bloque de instrucciones mientras que
una expresión booleana especificada se evalúa como true . Como esa expresión se
evalúa antes de cada ejecución del bucle, un bucle while se ejecuta cero o varias veces.
La instrucción while es diferente de un bucle do, que se ejecuta una o varias veces.
C#
int n = 0;
while (n < 5)
Console.Write(n);
n++;
// Output:
// 01234
La instrucción for
La instrucción foreach
La instrucción do
La instrucción while
Vea también
Referencia de C#
Utilizar foreach con matrices
Iteradores
Instrucciones de selección if , else y
switch
Artículo • 17/02/2023 • Tiempo de lectura: 5 minutos
Instrucción if
Una instrucción if puede tener cualquiera de las dos formas siguientes:
Una instrucción if con una parte else selecciona una de las dos instrucciones
que se ejecutarán en función del valor de una expresión booleana, como se
muestra en el ejemplo siguiente:
C#
Console.WriteLine("Cold.");
else
Console.WriteLine("Perfect!");
Una instrucción if sin una parte else ejecuta el cuerpo solo si una expresión
booleana se evalúa como true , como se muestra en el ejemplo siguiente:
C#
DisplayMeasurement(45); // Output: The measurement value is 45
C#
if (char.IsUpper(ch))
else if (char.IsLower(ch))
else if (char.IsDigit(ch))
else
Instrucción switch
La instrucción switch selecciona una lista de instrucciones para ejecutarla en función de
la coincidencia de un patrón con una expresión de coincidencia, como se muestra en el
ejemplo siguiente:
C#
switch (measurement)
break;
break;
case double.NaN:
Console.WriteLine("Failed measurement.");
break;
default:
break;
) Importante
Para obtener información sobre los patrones admitidos por la instrucción switch ,
consulte Patrones.
En el ejemplo anterior también se muestra el caso default . El caso default especifica
las instrucciones que se ejecutarán cuando una expresión de coincidencia no coincida
con ningún otro patrón de caso. Si una expresión de coincidencia no coincide con
ningún patrón de caso y no hay ningún caso default , el control pasa por una
instrucción switch .
7 Nota
Puede especificar varios patrones de casos para una sección de una instrucción switch ,
como se muestra en el ejemplo siguiente:
C#
switch (measurement)
case < 0:
break;
default:
break;
Dentro de una instrucción switch , el control no puede pasar desde una sección switch a
la siguiente. Como se muestra en los ejemplos de esta sección, normalmente se usa la
instrucción break al final de cada sección switch para pasar el control desde una
instrucción switch . También puede usar las instrucciones return y throw para pasar el
control desde una instrucción switch . Para imitar el comportamiento de pasaje explícito
y pasar el control a otra sección switch, puede usar la instrucción goto.
En el contexto de una expresión, puede usar la expresión switch para evaluar una
expresión única a partir de una lista de expresiones candidatas basada en una
coincidencia de patrón con una expresión.
C#
break;
break;
default:
break;
La instrucción if
La instrucción switch
Vea también
Referencia de C#
Operador condicional ?:
Operadores lógicos
Patrones
Expresión switch
Agregar casos que faltan a la instrucción "switch" (regla de estilo IDE0010)
Instrucciones de salto: break , continue ,
return y goto
Artículo • 17/02/2023 • Tiempo de lectura: 9 minutos
Para obtener información sobre la instrucción throw que genera una excepción y
también transfiere el control sin condiciones, consulte throw.
Instrucción break
La instrucción break termina la instrucción de iteración contenedora más próxima (es
decir, los bucles for , foreach , while o do ) o la instrucción switch. La instrucción break
transfiere el control a la instrucción que hay a continuación de la instrucción finalizada,
si existe.
C#
int[] numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
foreach (int number in numbers)
if (number == 3)
break;
Console.Write($"{number} ");
Console.WriteLine();
// Output:
// 0 1 2
En los bucles anidados, la instrucción break termina solo el bucle más interno que la
contiene, como se muestra en el ejemplo siguiente:
C#
break;
Console.Write($"{inner} ");
Console.WriteLine();
// Output:
// 0
// 0 1
// 0 1 2
// 0 1 2 3
// 0 1 2 3 4
Cuando usa la instrucción switch dentro de un bucle, una instrucción break al final de
una sección switch transfiere el control solo fuera de la instrucción switch . El bucle que
contiene la instrucción switch no se ve afectado, como se muestra en el ejemplo
siguiente:
C#
switch (measurement)
break;
break;
case double.NaN:
Console.WriteLine("Failed measurement.");
break;
default:
break;
// Output:
// Failed measurement.
Instrucción continue
La instrucción continue inicia una nueva iteración de la instrucción de iteración
contenedora más próxima (es decir, los bucles for , foreach , while o do ), como se
muestra en el ejemplo siguiente:
C#
if (i < 3)
Console.WriteLine("skip");
continue;
Console.WriteLine("done");
// Output:
// Iteration 0: skip
// Iteration 1: skip
// Iteration 2: skip
// Iteration 3: done
// Iteration 4: done
Instrucción return
La instrucción return termina la ejecución de la función en la que aparece y devuelve el
control y el resultado de la función, si existe, al llamador.
C#
Console.WriteLine("First call:");
DisplayIfNecessary(6);
Console.WriteLine("Second call:");
DisplayIfNecessary(5);
if (number % 2 == 0)
return;
Console.WriteLine(number);
// Output:
// First call:
// Second call:
// 5
C#
Cuando la instrucción return tiene una expresión, esa expresión debe poderse convertir
implícitamente al tipo de valor devuelto de un miembro de función a menos que sea
async. La expresión devuelta de una función async debe poderse convertir
implícitamente al argumento de tipo de Task<TResult> o ValueTask<TResult>, el que
sea el tipo de valor devuelto de la función. Si el tipo de valor devuelto de una función
async es Task o ValueTask, se usa la instrucción return sin expresión.
De forma predeterminada, la instrucción return devuelve el valor de una expresión.
Puede devolver una referencia a una variable. Para ello, use la instrucción return con la
palabra clave ref, como se muestra en el ejemplo siguiente:
C#
found = 0;
if (predicate(numbers[i]))
Devoluciones de referencias
Los valores devueltos se pueden devolver por referencia (devuelve ref ). Un valor
devuelto de referencia permite que un método devuelva una referencia a una variable,
en lugar de un valor, al autor de una llamada. El autor de la llamada puede tratar la
variable devuelta como si se hubiera devuelto por valor o por referencia. El autor de la
llamada puede crear una variable que sea una referencia al valor devuelto, lo que se
conoce como una referencia local. Un valor devuelto de referencia significa que un
método devuelve una referencia (o un alias) a alguna variable. El ámbito de esa variable
debe incluir el método. La duración de la variable debe extenderse más allá de la
devolución del método. Las modificaciones en el valor del método devuelto por el autor
de la llamada se realizan en la variable devuelta por el método.
Declarar que un método devuelve un valor devuelto de referencia indica que el método
devuelve un alias a una variable. La intención del diseño suele ser que el código de
llamada acceda a esa variable a través del alias, incluso para modificarla. Por tanto, los
métodos devueltos por referencia no pueden tener el tipo de valor devuelto void .
El valor devuelto ref es un alias para otra variable en el ámbito del método llamado.
Puede interpretar cualquier uso del valor devuelto tipo ref como si se usara la variable a
la que se asigna el alias:
Al asignar su valor, se asigna un valor a la variable a la que se asigna el alias.
Al leer su valor, se lee un valor a la variable a la que se asigna el alias.
Si la devolución se realiza por referencia, entonces devuelve un alias a esa misma
variable.
Si pasa el valor a otro método por referencia, pasará una referencia a la variable a
la que se asigna el alias.
Al asignar un alias local tipo ref, crea un alias para la misma variable.
Un valor devuelto ref debe ser ref_safe_to_escape respecto al método que realiza la
llamada. Esto significa lo siguiente:
El valor devuelto debe tener una duración que se extienda más allá de la ejecución
del método. En otras palabras, no puede tratarse de una variable local del método
que la devuelve. Puede ser una instancia o un campo estático de una clase, o
puede ser un argumento pasado al método. Al intentar devolver una variable local,
se genera el error del compilador CS8168, "No se puede devolver por referencia la
variable local 'obj' porque no es de tipo ref".
El valor devuelto no puede ser el literal null . Un método con un valor devuelto de
referencia puede devolver un alias a una variable cuyo valor es actualmente el
valor null (sin instancias) o un tipo de valor que admite un valor NULL para un
tipo de valor.
El valor devuelto no puede ser una constante, un miembro de enumeración, el
valor devuelto por valor desde una propiedad o un método de class o struct .
C#
// ...method implementation...
return ref p;
Instrucción goto
La instrucción goto transfiere el control a una instrucción marcada por una etiqueta,
como se muestra en el ejemplo siguiente:
C#
["A"] = new[]
new[] { 1, 2, 3, 4 },
new[] { 4, 3, 2, 1 }
},
["B"] = new[]
new[] { 5, 6, 7, 8 },
new[] { 8, 7, 6, 5 }
},
};
CheckMatrices(matrices, 4);
if (matrix[row][col] == target)
goto Found;
continue;
Found:
// Output:
// Found 4 in matrix A.
Como se muestra en el ejemplo anterior, se puede usar la instrucción goto para salir de
un bucle anidado.
Sugerencia
También puede usar la instrucción goto en la instrucción switch para transferir el control
a una sección switch con una etiqueta case constante, como se muestra en el ejemplo
siguiente:
C#
using System;
Plain,
WithMilk,
WithIceCream,
Console.WriteLine(CalculatePrice(CoffeeChoice.Plain)); // output:
10.0
Console.WriteLine(CalculatePrice(CoffeeChoice.WithMilk)); //
output: 15.0
Console.WriteLine(CalculatePrice(CoffeeChoice.WithIceCream)); //
output: 17.0
decimal price = 0;
switch (choice)
case CoffeeChoice.Plain:
price += 10.0m;
break;
case CoffeeChoice.WithMilk:
price += 5.0m;
case CoffeeChoice.WithIceCream:
price += 7.0m;
return price;
La instrucción break
La instrucción continue
La instrucción return
La instrucción goto
Vea también
Referencia de C#
Instrucción yield
instrucción lock: asegúrese del acceso
exclusivo a un recurso compartido.
Artículo • 17/02/2023 • Tiempo de lectura: 2 minutos
C#
lock (x)
// Your code...
C#
object __lockObj = x;
try
// Your code...
finally
if (__lockWasTaken) System.Threading.Monitor.Exit(__lockObj);
Puesto que el código usa un bloque try... finally, el bloqueo se libera aunque se
produzca una excepción dentro del cuerpo de una instrucción lock .
Instrucciones
Al sincronizar el acceso del subproceso al recurso compartido, bloquee una instancia
dedicada de objeto (por ejemplo, private readonly object balanceLock = new
object(); ) u otra instancia cuyo empleo como objeto de bloqueo sea poco probable
por parte de elementos no relacionados del código. Evite el uso de la misma instancia
de objeto de bloqueo para distintos recursos compartidos, ya que se podría producir un
interbloqueo o una contención de bloqueo. En particular, evite utilizar los siguientes
tipos como objetos de bloqueo:
Ejemplo
En el ejemplo siguiente se define una clase Account que sincroniza el acceso a su campo
privado balance mediante el bloqueo de una instancia dedicada balanceLock . El empleo
de la misma instancia para bloquear garantiza que el campo balance no sea actualizado
al mismo tiempo por dos subprocesos que intentan llamar a los métodos Debit o
Credit simultáneamente.
C#
using System;
using System.Threading.Tasks;
if (amount < 0)
{
decimal appliedAmount = 0;
lock (balanceLock)
balance -= amount;
appliedAmount = amount;
return appliedAmount;
if (amount < 0)
{
lock (balanceLock)
balance += amount;
lock (balanceLock)
return balance;
class AccountTest
await Task.WhenAll(tasks);
// Output:
if (amount >= 0)
account.Credit(amount);
else
account.Debit(Math.Abs(amount));
Vea también
Referencia de C#
System.Threading.Monitor
System.Threading.SpinLock
System.Threading.Interlocked
Información general sobre las primitivas de sincronización
Introducción a System.Threading.Channels
Caracteres especiales de C#
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
Esta sección solo incluye los tokens que no son operadores. Consulte la sección de
operadores para ver todos los operadores.
Consulte también
Referencia de C#
Guía de programación de C#
Interpolación de cadenas mediante $
Artículo • 17/02/2023 • Tiempo de lectura: 8 minutos
El carácter especial $ identifica un literal de cadena como una cadena interpolada. Una
cadena interpolada es un literal de cadena que puede contener expresiones de
interpolación. Cuando una cadena interpolada se resuelve en una cadena de resultado,
los elementos con expresiones de interpolación se reemplazan por las representaciones
de cadena de los resultados de la expresión.
C#
// Composite formatting:
// String interpolation:
C#
{<interpolationExpression>[,<alignment>][:<formatString>]}
Los elementos entre corchetes son opcionales. En esta tabla se describe cada elemento:
Elemento Descripción
C#
Console.WriteLine($"|{"Left",-7}|{"Right",7}|");
// |Left | Right|
A partir de C# 11, las expresiones interpoladas pueden incluir nuevas líneas. El texto
entre { y } debe ser válido en C#, por lo tanto, puede incluir nuevas líneas que mejoran
la legibilidad. En el ejemplo siguiente se muestra cómo las nuevas líneas pueden
mejorar la legibilidad de una expresión que implica la coincidencia de patrones:
C#
safetyScore switch
}";
Además, a partir de C# 11, puede usar un literal de cadena sin formato para la cadena
de formato:
C#
int X = 2;
int Y = 3;
Console.WriteLine(pointMessage);
Puede usar varios caracteres $ en un literal de cadena sin formato interpolada para
insertar caracteres { y } en la cadena de salida sin escaparlos:
C#
int X = 2;
int Y = 3;
Console.WriteLine(pointMessage);
Puede probar estas características mediante el SDK de .NET 7. O bien, si tiene el SDK de
.NET 6.00.200 o posterior, puede establecer el elemento <LangVersion> del archivo
csproj en preview .
Caracteres especiales
Para incluir una llave ("{" o "}") en el texto generado por una cadena interpolada, use dos
llaves ("{{" o "}}"). Para más información, vea Llaves de escape.
Como los dos puntos (":") tienen un significado especial en un elemento de expresión
de interpolación, para poder usar un operador condicional en una expresión de
interpolación, incluya esa expresión entre paréntesis.
En el siguiente ejemplo se muestra cómo incluir una llave en una cadena de resultado.
También se muestra cómo usar un operador condicional:
C#
Console.WriteLine($"He asked, \"Is your name {name}?\", but didn't wait for
a reply :-{{");
// He asked, "Is your name Horace?", but didn't wait for a reply :-{
Las cadenas textuales interpoladas comienzan por el carácter $ , seguido del carácter @ .
Los tokens $ y @ se pueden usar en cualquier orden; tanto $@"..." como @$"..." son
cadenas textuales interpoladas válidas. Para más información sobre las cadenas
textuales, consulte los artículos sobre cadenas e Identificadores textuales.
C#
System.Globalization.CultureInfo.CurrentCulture =
System.Globalization.CultureInfo.GetCultureInfo("nl-NL");
Console.WriteLine($"{System.Globalization.CultureInfo.CurrentCulture,-10}
{messageInCurrentCulture}");
Console.WriteLine($"{specificCulture,-10} {messageInSpecificCulture}");
Console.WriteLine($"{"Invariant",-10} {messageInInvariantCulture}");
Otros recursos
Si no está familiarizado con la interpolación de cadenas, consulte el tutorial interactivo
Interpolación de cadenas en C#. También puede consultar otro tutorial sobre la
interpolación de cadenas en C#. En ese tutorial se muestra el uso de cadenas
interpoladas para generar cadenas con formato.
7 Nota
Vea también
Simplificación de la interpolación (regla de estilo IDE0071)
Referencia de C#
Caracteres especiales de C#
Cadenas
Cadenas con formato numérico estándar
Formatos compuestos
String.Format
Texto textual: @ en variables, atributos y
literales de cadena
Artículo • 17/02/2023 • Tiempo de lectura: 3 minutos
C#
C#
string filename1 = @"c:\documents\files\u0066.txt";
Console.WriteLine(filename1);
Console.WriteLine(filename2);
// c:\documents\files\u0066.txt
// c:\documents\files\u0066.txt
C#
Console.WriteLine(s1);
Console.WriteLine(s2);
3. Para permitir que el compilador distinga entre los atributos en caso de conflicto de
nomenclatura. Un atributo es una clase que deriva de Attribute. Normalmente, su
nombre de tipo incluye el sufijo Attribute, aunque el compilador no exige el
cumplimiento de esta convención. Es posible hacer referencia al atributo en el
código mediante su nombre de tipo completo (por ejemplo, [InfoAttribute] ) o
mediante su nombre abreviado (por ejemplo, [Info] ). Pero se produce un
conflicto de nomenclatura si dos nombres abreviados de tipo de atributo son
idénticos, y un nombre de tipo incluye el sufijo Attribute y el otro no. Por ejemplo,
el código siguiente produce un error al compilarse porque el compilador no puede
determinar si el atributo Info o InfoAttribute se aplica a la clase Example . Para
obtener más información, vea CS1614.
C#
using System;
[AttributeUsage(AttributeTargets.Class)]
information = info;
[AttributeUsage(AttributeTargets.Method)]
information = info;
Consulte también
Referencia de C#
Guía de programación de C#
Caracteres especiales de C#
Atributos de nivel de ensamblado
interpretados por el compilador de C#
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
La mayoría de los atributos se aplican a elementos específicos del lenguaje, como las
clases o los métodos, aunque algunos atributos son globales (se aplican a todo un
ensamblado o módulo). Por ejemplo, el atributo AssemblyVersionAttribute se puede
usar para insertar información de versión en un ensamblado, como en este ejemplo:
C#
[assembly: AssemblyVersion("1.0.0.0")]
Atributos informativos
Puede usar atributos informativos para proporcionar información adicional de la
empresa o el producto para un ensamblado. En la tabla siguiente se muestran los
atributos informativos definidos en el espacio de nombres System.Reflection.
Atributo Propósito
C#
TraceMessage("Something happened.");
// Sample Output:
Nombres de miembro
Se puede utilizar el atributo CallerMemberName para evitar especificar el nombre de
miembro como un argumento String para el método llamado. Mediante esta técnica,
se evita el problema de que la refactorización de cambio de nombre no cambie los
valores String . Esta ventaja es especialmente útil para las siguientes tareas:
Expresiones de argumento
Se debe usar System.Runtime.CompilerServices.CallerArgumentExpressionAttribute
cuando quiera que la expresión se pase como argumento. Es posible que las bibliotecas
de diagnóstico quieran proporcionar más detalles sobre las expresiones que se pasan a
los argumentos. Al proporcionar la expresión que desencadenó el diagnóstico, además
del nombre del parámetro, los desarrolladores tienen más detalles sobre la condición
que desencadenó el diagnóstico. Esa información adicional facilita la corrección.
C#
if (!condition)
C#
func();
CLI de .NET
Este atributo permite escribir utilidades de diagnóstico que proporcionan más detalles.
Los desarrolladores pueden comprender más rápidamente qué cambios son necesarios.
También puede usar CallerArgumentExpressionAttribute a fin de determinar qué
expresión se usó como receptor para los métodos de extensión. El método siguiente
muestrea una secuencia a intervalos regulares. Si la secuencia tiene menos elementos
que la frecuencia, notifica un error:
C#
int i = 0;
if (i++ % frequency == 0)
C#
CLI de .NET
Vea también
Argumentos opcionales y con nombre
System.Reflection
Attribute
Atributos
Atributos para el análisis estático de
estado NULL interpretados por el
compilador de C#
Artículo • 15/02/2023 • Tiempo de lectura: 17 minutos
En este artículo se proporciona una breve descripción de cada uno de los atributos de
tipo de referencia que acepta valores NULL y cómo usarlos.
Comencemos con un ejemplo. Imagine que la biblioteca tiene la siguiente API para
recuperar una cadena de recursos. Este método se compiló originalmente en un
contexto de tipo "oblivious" que admite un valor NULL:
C#
if (_messageMap.ContainsKey(key))
message = _messageMap[key];
else
message = null;
En el ejemplo anterior se sigue el conocido patrón de Try* en .NET. Hay dos parámetros
de referencia para esta API: key y message . Esta API tiene las siguientes reglas
relacionadas con el estado NULL de estos parámetros:
Los autores de la llamada no deben pasar null como argumento para key .
Los autores de la llamada pueden pasar una variable cuyo valor sea null como
argumento de message .
Si el método TryGetMessage devuelve true , el valor de message no es NULL. Si el
valor devuelto es false, , el valor de message es NULL.
La regla para key se puede expresar de forma sucinta: key debe ser un tipo de
referencia que no acepta valores NULL. El parámetro message es más complejo. Permite
una variable que sea null como argumento, pero garantiza que, si se ejecuta
correctamente, el argumento out no sea null . En estos escenarios, necesita un
vocabulario más completo para describir las expectativas. El atributo NotNullWhen , que
se describe a continuación, describe el estado NULL del argumento utilizado para el
parámetro message .
7 Nota
Las descripciones anteriores son una referencia rápida a lo que hace cada atributo. En
las secciones siguientes se describe el comportamiento y el significado de estos
atributos de forma más exhaustiva.
C#
C#
[AllowNull]
de acceso get tiene un valor devuelto, pero no parámetros. Por tanto, el atributo
AllowNull solo se aplica al descriptor de acceso set .
1. El contrato general para esa variable es que no debe ser null , por lo que quiere
un tipo de referencia que no acepte valores NULL.
2. Existen escenarios para que un autor de llamada pase null como argumento,
aunque no son el uso más común.
En la mayoría de los casos necesitará este atributo para las propiedades, o bien para los
argumentos in , out y ref . El atributo AllowNull es la mejor opción cuando una
variable normalmente no es NULL, pero debe permitir null como condición previa.
Compare esto con los escenarios donde se usa DisallowNull : este atributo se usa para
especificar que un argumento de un tipo de referencia que admite un valor NULL no
deba ser null . Considere una propiedad donde null es el valor predeterminado, pero
los clientes solo pueden establecerla en un valor que no sea NULL. Observe el código
siguiente:
C#
string _comment;
C#
[DisallowNull]
string? _comment;
C#
Probablemente haya escrito un método como este para devolver null cuando no se
encuentra el nombre que se busca. null indica claramente que no se ha encontrado el
registro. En este ejemplo, es probable que cambie el tipo de valor devuelto de Customer
a Customer? . Al declarar el valor devuelto como un tipo de referencia que admite un
valor NULL, se especifica claramente la intención de esta API:
C#
Por motivos que se tratan en Tipos de referencia que aceptan valores NULL - Genéricos,
es posible que esa técnica no produzca el análisis estático que coincida con la API.
Puede tener un método genérico que siga un patrón similar:
C#
C#
[return: MaybeNull]
El código anterior informa a los autores de la llamada de que el valor devuelto puede
realmente ser NULL. También informa al compilador de que el método puede devolver
una expresión null aunque el tipo no acepte valores NULL. Si tiene un método
genérico que devuelve una instancia de su parámetro de tipo, T , puede expresar que
nunca devuelve null mediante el atributo NotNull .
También puede especificar que un valor devuelto o un argumento no sean NULL aunque
el tipo sea un tipo de referencia que admite un valor NULL. El método siguiente es un
método auxiliar que se produce si su primer argumento es null :
C#
C#
Console.WriteLine(message.Length);
Después de habilitar los tipos de referencia NULL, querrá asegurarse de que el código
anterior se compila sin advertencias. Cuando el método devuelve un valor, se garantiza
que el parámetro value no es NULL. Pero es aceptable llamar a ThrowWhenNull con una
referencia nula. Puede convertir a value en un tipo de referencia que acepte valores
NULL y agregar la condición posterior NotNull a la declaración del parámetro:
C#
MaybeNull: un valor devuelto que no acepta valores NULL puede ser NULL.
NotNull: un valor devuelto que admite un valor NULL nunca será NULL.
C#
C#
if (!string.IsNullOrEmpty(userInput))
Otro uso de estos atributos es el patrón Try* . Las condiciones posteriores para los
argumentos ref y out se comunican a través del valor devuelto. Considere este
método mostrado anteriormente (en un contexto deshabilitado que admite un valor
NULL):
C#
if (_messageMap.ContainsKey(key))
message = _messageMap[key];
else
message = null;
El método anterior sigue una expresión de .NET típica: el valor devuelto indica si
message se ha establecido en el valor encontrado o, si no se ha encontrado ningún
En un contexto habilitado que admite un valor NULL, puede comunicar esa expresión
mediante el atributo NotNullWhen . Al anotar parámetros para los tipos de referencia que
admiten un valor NULL, convierta message en string? y agregue un atributo:
C#
if (_messageMap.ContainsKey(key))
message = _messageMap[key];
else
message = null;
Hay un atributo final que también puede necesitar. En ocasiones, el estado NULL de un
valor devuelto depende del estado NULL de uno o más argumentos. Estos métodos
devolverán un valor no NULL siempre que determinados argumentos no sean null .
Para anotar correctamente estos métodos, use el atributo NotNullIfNotNull . Observe el
método siguiente:
C#
C#
Esto también funciona, pero a menudo obliga a los autores de la llamada a implementar
comprobaciones de null adicionales. El contrato es que el valor devuelto solo será
null cuando el argumento url sea null . Para expresar ese contrato, tendría que
anotar este método como se muestra en el código siguiente:
C#
[return: NotNullIfNotNull(nameof(url))]
C#
public Container()
Helper();
Helper();
_optionalMessage = message;
[MemberNotNull(nameof(_uniqueIdentifier))]
_uniqueIdentifier = DateTime.Now.Ticks.ToString();
C#
[DoesNotReturn]
if (containedField is null)
FailFast();
_field = containedField;
if (isNull)
FailFastIf(containedField == null);
_field = containedField;
Cuando el valor del argumento coincide con el valor del constructor DoesNotReturnIf , el
compilador no realiza ningún análisis de estado NULL después de ese método.
Resumen
Agregar tipos de referencia que aceptan valores NULL proporciona un vocabulario
inicial para describir las expectativas de las API para las variables que podrían ser null .
Los atributos proporcionan un vocabulario más completo para describir el estado NULL
de las variables como condiciones previas y posteriores. Estos atributos describen con
más claridad las expectativas y proporcionan una mejor experiencia para los
desarrolladores que usan las API.
A medida que actualice las bibliotecas para un contexto que admite un valor NULL,
agregue estos atributos para guiar a los usuarios de las API al uso correcto. Estos
atributos ayudan a describir de forma completa el estado NULL de los argumentos y los
valores devueltos.
Atributo Conditional
El atributo Conditional hace que la ejecución de un método dependa de un
identificador de preprocesamiento. El atributo Conditional es un alias de
ConditionalAttribute y se puede aplicar a un método o a una clase de atributo.
C#
#define TRACE_ON
using System.Diagnostics;
namespace AttributeExamples;
[Conditional("TRACE_ON")]
Console.WriteLine(msg);
Trace.Msg("Now in Main...");
Console.WriteLine("Done.");
El atributo Conditional se suele usar con el identificador DEBUG para habilitar las
funciones de seguimiento y de registro para las compilaciones de depuración, pero no
en las compilaciones de versión, como se muestra en el ejemplo siguiente:
C#
[Conditional("DEBUG")]
C#
[Conditional("A"), Conditional("B")]
// ...
C#
[Conditional("DEBUG")]
string text;
this.text = text;
class SampleClass
System.Console.WriteLine(i.ToString());
Atributo Obsolete
El atributo Obsolete marca un elemento de código como ya no recomendado para su
uso. El uso de una entidad marcada como obsoleta genera una advertencia o un error.
El atributo Obsolete es un atributo de uso único y se puede aplicar a cualquier entidad
que admita atributos. Obsolete es un alias de ObsoleteAttribute.
C#
namespace AttributeExamples
public class A
public class B
// Generates 2 warnings:
A a = new A();
B b = new B();
b.NewMethod();
// b.OldMethod();
C#
public class B
Atributo SetsRequiredMembers
El atributo SetsRequiredMembers informa al compilador de que un constructor establece
todos los miembros required de esa clase o estructura. El compilador supone que
cualquier constructor con el atributo
System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute inicializa todos los
miembros required . Cualquier código que invoque este constructor no necesita
inicializadores de objeto para establecer los miembros necesarios. Esto es
principalmente útil para los registros posicionales y los constructores principales.
Atributo AttributeUsage
El atributo AttributeUsage determina cómo se puede usar una clase de atributo
personalizado. AttributeUsageAttribute es un atributo que se aplica a las definiciones de
atributo personalizado. El atributo AttributeUsage permite controlar lo siguiente:
C#
[AttributeUsage(AttributeTargets.All,
AllowMultiple = false,
Inherited = true)]
C#
[AttributeUsage(AttributeTargets.All)]
C#
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
C#
class MyClass
[NewPropertyOrField]
[field: NewPropertyOrField]
C#
[MultiUse]
[MultiUse]
class Class1 { }
[MultiUse, MultiUse]
class Class2 { }
Si Inherited se establece en false , las clases que se derivan de una clase con atributos
no heredan el atributo. Por ejemplo:
C#
[NonInherited]
class BClass { }
También puede usar estas palabras clave para especificar dónde se debe aplicar un
atributo. Por ejemplo, puede usar el especificador field: para agregar un atributo al
campo de respaldo de una propiedad implementada automáticamente. También puede
usar el especificador field: , property: o param: para aplicar un atributo a cualquiera
de los elementos generados a partir de un registro posicional. Para obtener un ejemplo,
vea Sintaxis posicional para la definición de propiedades.
Atributo AsyncMethodBuilder
El atributo System.Runtime.CompilerServices.AsyncMethodBuilderAttribute se agrega a
un tipo que puede ser un tipo de valor devuelto asincrónico. El atributo especifica el
tipo que compila la implementación del método asincrónico cuando se devuelve el tipo
especificado desde un método asincrónico. El atributo AsyncMethodBuilder se puede
aplicar a un tipo que:
Una propiedad Task legible que devuelve el tipo de valor devuelto asincrónico.
C#
where TStateMachine :
System.Runtime.CompilerServices.IAsyncStateMachine
C#
where TStateMachine :
System.Runtime.CompilerServices.IAsyncStateMachine
C#
where TAwaiter :
System.Runtime.CompilerServices.ICriticalNotifyCompletion
where TStateMachine :
System.Runtime.CompilerServices.IAsyncStateMachine
Para obtener información sobre los generadores de métodos asincrónicos, lea sobre los
generadores siguientes proporcionados por .NET:
System.Runtime.CompilerServices.AsyncTaskMethodBuilder
System.Runtime.CompilerServices.AsyncTaskMethodBuilder<TResult>
System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder
System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder<TResult>
En C# 10 y versiones posteriores, el atributo AsyncMethodBuilder se puede aplicar a un
método asincrónico para invalidar el generador de ese tipo.
Atributos InterpolatedStringHandler y
InterpolatedStringHandlerArguments
A partir de C# 10, se usan estos atributos para especificar que un tipo es un controlador
de cadenas interpoladas. La biblioteca de .NET 6 ya incluye
System.Runtime.CompilerServices.DefaultInterpolatedStringHandler para escenarios en
los que se usa una cadena interpolada como argumento para un parámetro string . Es
posible que tenga otras instancias en las que quiera controlar cómo se procesan las
cadenas interpoladas. Aplique
System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute al tipo que
implementa el controlador. Aplique
System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute a los
parámetros del constructor de ese tipo.
Atributo ModuleInitializer
A partir de C# 9, el atributo ModuleInitializer marca un método al que el entorno de
ejecución llama cuando se carga el ensamblado. ModuleInitializer es un alias de
ModuleInitializerAttribute.
Sea estático.
No tenga parámetros.
Devuelve void .
Sea accesible desde el módulo contenedor, es decir, internal o public .
No sea un método genérico.
No esté incluido en una clase genérica.
No sea una función local.
C#
using System;
Console.WriteLine(ModuleInitializerExampleModule.Text);
C#
using System.Runtime.CompilerServices;
[ModuleInitializer]
[ModuleInitializer]
Atributo SkipLocalsInit
A partir de C# 9, el atributo SkipLocalsInit impide que el compilador establezca la
marca .locals init cuando se realiza la emisión a metadatos. SkipLocalsInit es un
atributo de uso único y se puede aplicar a un método, una propiedad, una clase, una
estructura, una interfaz o un módulo, pero no a un ensamblado. SkipLocalsInit es un
alias de SkipLocalsInitAttribute.
La marca .locals init hace que el CLR inicialice todas las variables locales declaradas
en un método en sus valores predeterminados. Como el compilador también se asegura
de que nunca se use una variable antes de asignarle un valor, .locals init no suele ser
necesario. Pero la inicialización en cero adicional puede afectar al rendimiento en
algunos escenarios, como cuando se usa stackalloc para asignar una matriz en la pila. En
esos casos, puede agregar el atributo SkipLocalsInit . Si se aplica directamente a un
método, el atributo afecta a ese método y a todas sus funciones anidadas, incluidas las
expresiones lambda y las funciones locales. Si se aplica a un tipo o un módulo, afecta a
todos los métodos anidados. Este atributo no afecta a los métodos abstractos, pero sí al
código generado para la implementación.
Este atributo necesita la opción del compilador AllowUnsafeBlocks. Este requisito indica
que, en algunos casos, el código podría ver la memoria no asignada (por ejemplo, la
lectura de la memoria asignada a la pila no inicializada).
C#
[SkipLocalsInit]
Console.WriteLine(numbers[i]);
//0
//0
//0
//168
//0
//-1271631451
//32767
//38
//0
//0
//0
//38
XML
<PropertyGroup>
...
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
Vea también
Attribute
System.Reflection
Atributos
Reflexión
Código no seguro, tipos de puntero y
punteros de función
Artículo • 15/02/2023 • Tiempo de lectura: 14 minutos
Tipos de puntero
En un contexto no seguro, un tipo puede ser un tipo de puntero, además de un tipo de
valor o un tipo de referencia. Una declaración de tipos de puntero toma una de las
siguientes formas:
C#
type* identifier;
C#
C#
int* myVariable;
Hay varios ejemplos de punteros en los artículos sobre la instrucción fixed. En el ejemplo
siguiente se usa la palabra clave unsafe y la instrucción fixed y se muestra cómo
incrementar un puntero interior. Puede pegar este código en la función Main de una
aplicación de consola para ejecutarla. Estos ejemplos se deben compilar con el conjunto
de opciones del compilador AllowUnsafeBlocks.
C#
unsafe
// Must pin object on heap so that it doesn't move while using interior
pointers.
int* p2 = p;
Console.WriteLine(*p2);
p2 += 1;
Console.WriteLine(*p2);
p2 += 1;
Console.WriteLine(*p2);
Console.WriteLine("--------");
Console.WriteLine(*p);
*p += 1;
Console.WriteLine(*p);
*p += 1;
Console.WriteLine(*p);
Console.WriteLine("--------");
Console.WriteLine(a[0]);
/*
Output:
10
20
30
--------
10
11
12
--------
12
*/
Operador/Instrucción Usar
[] Indiza un puntero.
Instrucción fixed Fija provisionalmente una variable para que pueda encontrarse su
dirección.
Para obtener más información sobre los operadores relacionados con el puntero, vea
Operadores relacionados con el puntero.
C#
unsafe
// Convert to byte:
byte* p = (byte*)&number;
System.Console.WriteLine();
/* Output:
*/
C#
En el código seguro, un struct de C# que contiene una matriz no contiene los elementos
de matriz. En su lugar, el struct contiene una referencia a los elementos. Puede insertar
una matriz de tamaño fijo en un struct cuando se usa en un bloque de código no
seguro.
C#
C#
unsafe
*charPtr = 'A';
char c = example.buffer.fixedBuffer[0];
Console.WriteLine(c);
example.buffer.fixedBuffer[0] = 'B';
Console.WriteLine(example.buffer.fixedBuffer[0]);
El tamaño de la matriz char de 128 elementos es 256 bytes. Los búferes char de tamaño
fijo siempre admiten 2 bytes por carácter, independientemente de la codificación. Este
tamaño de matriz es el mismo, incluso cuando se calculan las referencias de los búferes
char a las estructuras o métodos de API con CharSet = CharSet.Auto o CharSet =
CharSet.Ansi . Para obtener más información, vea CharSet.
En el ejemplo anterior se muestra el acceso a campos fixed sin anclar. Otra matriz de
tamaño fijo común es la matriz bool. Los elementos de una matriz bool siempre tienen
1 byte de tamaño. Las matrices bool no son adecuadas para crear matrices de bits o
búferes.
C#
C#
[CompilerGenerated]
[UnsafeValueType]
[FixedBuffer(typeof(char), 128)]
Los búferes de tamaño fijo son diferentes de las matrices normales en los siguientes
puntos:
En este ejemplo se usa la palabra clave unsafe, que permite el uso de punteros en el
método Copy . La instrucción fixed se usa para declarar punteros a las matrices de origen
y destino. La instrucción fixed ancla la ubicación de las matrices de origen y destino en
memoria, para que no puedan ser desplazadas por la recolección de elementos no
utilizados. Estos bloques de memoria para las matrices se desanclan cuando finaliza el
bloque fixed . Como el método Copy de este ejemplo usa la palabra clave unsafe , se
debe compilar con la opción del compilador AllowUnsafeBlocks.
Este ejemplo accede a los elementos de ambas matrices con índices en lugar de con un
segundo puntero no administrado. La declaración de los punteros pSource y pTarget
ancla las matrices.
C#
// If the number of bytes from the offset to the end of the array is
// less than the number of bytes you want to copy, you cannot complete
// the copy.
// The following fixed statement pins the location of the source and
// collection.
byteArray1[i] = (byte)i;
System.Console.WriteLine("\n");
System.Console.WriteLine("\n");
// beginning of byteArray2.
// The offset specifies where the copying begins in the source array.
System.Console.WriteLine("\n");
/* Output:
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
90 91 92 93 94 95 96 97 98 99
*/
Punteros de función
C# proporciona tipos delegate para definir objetos de puntero de función seguros. La
invocación de un delegado implica la creación de instancias de un tipo derivado de
System.Delegate y la realización de una llamada al método virtual a su método Invoke .
Esta llamada virtual utiliza la instrucción de IL callvirt . En las rutas de acceso de
código crítico para el rendimiento, el uso de la instrucción de IL calli es más eficaz.
C#
public static T Combine<T>(Func<T, T, T> combinator, T left, T right) =>
combinator(left, right);
combinator(left, right);
En el código siguiente se muestra cómo se declara una función local estática y se invoca
el método UnsafeCombine con un puntero a esa función local:
C#
C#
public static T ManagedCombine<T>(delegate* managed<T, T, T> combinator, T
left, T right) =>
combinator(left, right);
combinator(left, right);
combinator(left, right);
combinator(left, right);
combinator(left, right);
combinator(left, right);
Ambos contextos se pueden especificar en el nivel de proyecto (fuera del código fuente
de C#). La directiva #nullable controla los contextos de anotación y advertencia y tiene
prioridad sobre la configuración de nivel de proyecto. Una directiva establece los
contextos que controla hasta que otra directiva la invalida o hasta el final del archivo de
código fuente.
Compilación condicional
Para controlar la compilación condicional se usan cuatro directivas de preprocesador:
C#
#if DEBUG
Console.WriteLine("Debug version");
#endif
C#
#if !MYTEST
#endif
Puede usar los operadores == (igualdad) y != (desigualdad) para comprobar los valores
bool true o false . true significa que el símbolo está definido. La instrucción #if DEBUG
tiene el mismo significado que #if (DEBUG == true) . Puede usar los operadores && (y),
|| (o) y ! (no) para evaluar si se han definido varios símbolos. Es posible agrupar símbolos
y operadores mediante paréntesis.
#if , junto con las directivas #else , #elif , #endif , #define y #undef , permite incluir o
Una directiva condicional que empieza con una directiva #if debe terminar de forma
explícita con una directiva #endif . #define permite definir un símbolo. Al usar el
símbolo como la expresión que se pasa a la directiva #if , la expresión se evalúa como
true . También se puede definir un símbolo con la opción del compilador
C#
#define VC7
//...
#if DEBUG
Console.WriteLine("Debug build");
#elif VC7
#endif
#else permite crear una directiva condicional compuesta, de modo que, si ninguna de
las expresiones de las directivas #if o #elif (opcional) anteriores se evalúan como
true , el compilador evaluará todo el código entre #else y la directiva #endif siguiente.
#endif especifica el final de una directiva condicional, que comienza con la directiva
#if .
de destino cuando se
especifica un TFM
específico del
sistema operativo)
7 Nota
En el caso de los proyectos que no son de estilo SDK, tendrá que configurar
manualmente los símbolos de compilación condicional para las diferentes
plataformas de destino en Visual Studio a través de las páginas de propiedades del
proyecto.
Otros símbolos predefinidos incluyen las constantes DEBUG y TRACE . Puede invalidar los
valores establecidos para el proyecto con #define . Por ejemplo, el símbolo DEBUG se
establece automáticamente según las propiedades de configuración de compilación
(modo de "depuración" o de "versión").
C#
#define MYTEST
using System;
Console.WriteLine("DEBUG is defined");
Console.WriteLine("MYTEST is defined");
#else
#endif
En el ejemplo siguiente se muestra cómo probar distintos marcos de destino para que
se puedan usar las API más recientes cuando sea posible:
C#
#if NET40
#else
#endif
//...
Definición de símbolos
Use las dos directivas de preprocesador siguientes para definir o anular la definición de
símbolos para la compilación condicional:
Usa #define para definir un símbolo. Si usa el símbolo como expresión que se pasa a la
directiva #if , la expresión se evaluará como true , como se muestra en el siguiente
ejemplo:
C#
#define VERBOSE
#if VERBOSE
#endif
7 Nota
La directiva #define no puede usarse para declarar valores constantes como suele
hacerse en C y C++. En C#, las constantes se definen mejor como miembros
estáticos de una clase o struct. Si tiene varias constantes de este tipo, puede
considerar la posibilidad de crear una clase "Constants" independiente donde
incluirlas.
Definición de regiones
Puede definir regiones de código que se pueden contraer en un esquema mediante las
dos directivas de preprocesador siguientes:
C#
#endregion
Un bloque #region se debe terminar con una directiva #endregion . Un bloque #region
no se puede superponer con un bloque #if . Pero, un bloque #region se puede anidar
en un bloque #if y un bloque #if se puede anidar en un bloque #region .
#error permite generar un error CS1029 definido por el usuario desde una ubicación
C#
7 Nota
#warning permite generar una advertencia del compilador CS1030 de nivel uno desde
una ubicación específica en el código. Por ejemplo:
C#
C#
class MainClass
int i;
int j;
#line default
char c;
float f;
string s;
double d;
Consola
Special(200,13): warning CS0168: The variable 'i' is declared but never used
Special(201,13): warning CS0168: The variable 'j' is declared but never used
La directiva #line hidden oculta las líneas sucesivas del depurador, de forma que,
cuando el desarrollador ejecuta paso a paso el código, cualquier línea entre #line
hidden y la siguiente directiva #line (siempre que no sea otra directiva #line hidden )
se depurará paso a paso por procedimientos. Esta opción también se puede usar para
permitir que ASP.NET diferencie entre el código generado por el equipo y el definido
por el usuario. Aunque ASP.NET es el consumidor principal de esta característica, es
probable que la usen más generadores de código fuente.
Una directiva #line hidden no afecta a los nombres de archivo ni a los números de línea
en el informe de errores. Es decir, si el compilador detecta un error en un bloque oculto,
notificará el nombre de archivo y número de línea actuales del error.
La directiva #line filename especifica el nombre de archivo que quiere que aparezca en
la salida del compilador. De forma predeterminada, se usa el nombre real del archivo de
código fuente. El nombre de archivo debe estar entre comillas dobles ("") y debe ir
precedido de un número de línea.
C#
/*34567*/int b = 0;
(1, 1) : la línea de inicio y la columna del primer carácter de la línea que sigue a la
directiva. En este ejemplo, la línea siguiente se notifica como línea 1, columna 1.
(5, 60) : la línea final y la columna de la región marcada.
10 : desplazamiento de columna para que la directiva #line surta efecto. En este
CLI de .NET
Los lenguajes específicos de dominio (DSL) suelen usar este formato para proporcionar
una mejor asignación desde el archivo de origen hasta la salida de C# generada. El uso
más común de esta directiva extendida #line es volver a asignar advertencias o errores
que aparecen en un archivo generado al origen original. Por ejemplo, considere esta
página de razor:
razor
@page "/"
Time: @DateTime.NowAndThen
C#
_builder.Add("Time: ");
_builder.Add(DateTime.NowAndThen);
CLI de .NET
Pragmas
#pragma proporciona al compilador instrucciones especiales para la compilación del
archivo en el que aparece. Las instrucciones deben ser compatibles con el compilador.
Es decir, no puede usar #pragma para crear instrucciones de preprocesamiento
personalizadas.
#pragma warning: se habilitan o deshabilitan las advertencias.
#pragma checksum: se genera una lista de comprobación.
C#
#pragma warning
#pragma warning puede habilitar o deshabilitar determinadas advertencias.
C#
7 Nota
disable surte efecto a partir de la siguiente línea del archivo de código fuente. La
C#
// pragma_warning.cs
using System;
[CLSCompliant(false)]
public class C
int i = 1;
[CLSCompliant(false)] // CS3021
public class D
int i = 1;
C#
C#
class TestClass
En esta sección se describen las opciones que interpreta el compilador de C#. Las
opciones se agrupan en artículos independientes en función de lo que controlan, por
ejemplo, las características del lenguaje, la generación de código y la salida. Use la tabla
de contenido para navegar por ellas.
En el archivo *.csproj
XML
<PropertyGroup>
<LangVersion>preview</LangVersion>
</PropertyGroup>
Para obtener más información sobre el procedimiento para establecer las opciones
en los archivos de proyecto, consulte el artículo Referencia de MSBuild para
proyectos del SDK de .NET.
) Importante
Esta sección solo se aplica a los proyectos de .NET Framework.
Además de los mecanismos descritos anteriormente, puede establecer las opciones del
compilador por medio de dos métodos más para los proyectos de .NET Framework:
Consola
-doc:DocFile.xml
Si usa la ventana Símbolo del sistema para desarrolladores de Visual Studio, todas las
variables de entorno necesarias se establecen automáticamente. Para obtener
información sobre cómo acceder a esta herramienta, vea Símbolo del sistema para
desarrolladores de Visual Studio.
Las opciones siguientes controlan cómo el compilador interpreta las características del
lenguaje. La nueva sintaxis de MSBuild se muestra en negrita. La sintaxis de csc.exe
anterior se muestra en code style .
CheckForOverflowUnderflow
La opción CheckForOverflowUnderflow controla el contexto de comprobación de
desbordamiento predeterminado que define el comportamiento del programa si el
aritmético de enteros se desborda.
XML
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
AllowUnsafeBlocks
La opción del compilador AllowUnsafeBlocks permite la compilación de código en el
que se usa la palabra clave unsafe. El valor predeterminado de esta opción es false , lo
que significa que no se permite el código no seguro.
XML
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Para obtener más información sobre el código no seguro, vea Código no seguro y
punteros.
DefineConstants
La opción DefineConstants define símbolos en todos los archivos de código fuente del
programa.
XML
<DefineConstants>name;name2</DefineConstants>
Esta opción especifica los nombres de uno o más símbolos que quiera definir. La opción
DefineConstants tiene el mismo efecto que la directiva del preprocesador #define, salvo
que la opción del compilador está en vigor para todos los archivos del proyecto. Un
símbolo permanece definido en un archivo de origen hasta que una directiva #undef en
el archivo de origen quita la definición. Cuando usa la opción -define , una directiva
#undef en un archivo no tiene ningún efecto en otros archivos de código fuente del
proyecto. Los símbolos creados por esta opción se pueden usar con #if, #else, #elif y
#endif para compilar los archivos de origen condicionalmente. El propio compilador de
C# no define ningún símbolo o macro que puede usar en su código fuente; todas las
definiciones de símbolo deben definirse por el usuario.
7 Nota
El valor de la directiva #define de C# no permite que se le proporcione un valor a
un símbolo, como sucede en lenguajes como C++. Por ejemplo, #define no puede
usarse para crear una macro o para definir una constante. Si necesita definir una
constante, use una variable enum . Si quiere crear una macro de estilo de C++,
considere alternativas como genéricos. Como las macros son notoriamente
propensas a errores, C# deshabilita su uso pero proporciona alternativas más
seguras.
LangVersion
Hace que el compilador acepte solo la sintaxis que se incluye en la especificación
elegida del lenguaje C#.
XML
<LangVersion>9.0</LangVersion>
Valor Significado
latest El compilador acepta la sintaxis de la última versión del compilador (incluida las
versión secundaria).
latestMajor
El compilador acepta la sintaxis de la versión principal más reciente del
o bien compilador.
default
9.0 El compilador solo acepta la sintaxis que se incluye en C# 9 o versiones anteriores.
ISO-2
El compilador acepta solo la sintaxis que se incluye en ISO/IEC 23270:2006 C# (2.0).
o bien 2
ISO-1
El compilador acepta solo la sintaxis que se incluye en ISO/IEC 23270:2003 C#
o bien 1 (1.0/1.2).
) Importante
Por lo general, no se recomienda el valor latest . Con él, el compilador habilita las
características más recientes, incluso si esas características dependen de las
actualizaciones no incluidas en la plataforma de destino configurada. Sin esta
configuración, el proyecto usa la versión del compilador recomendada para la
plataforma de destino. Puede actualizar la plataforma de destino para acceder a las
características de lenguaje más recientes.
Los metadatos a los que hace referencia la aplicación de C# no están sujetos a la opción
del compilador LangVersion.
Para obtener otras formas de especificar la versión del lenguaje C#, vea Control de
versiones del lenguaje C#.
Para obtener información sobre cómo establecer esta opción del compilador mediante
programación, vea LanguageVersion.
C# 3.0 Decargar Especificación del lenguaje C#, versión 3.0: Microsoft Corporation
DOC
C# 1.2 Decargar Especificación del lenguaje C#, versión 1.2: Microsoft Corporation
DOC
C# 1.0 Decargar Especificación del lenguaje C#, versión 1.0: Microsoft Corporation
DOC
Versión del SDK mínima necesaria para admitir todas las
características del lenguaje
En la tabla siguiente se enumeran las versiones mínimas del SDK con el compilador de
C# que admite la versión del lenguaje correspondiente:
C# 1.0/1.2 Microsoft Visual Studio/Build Tools .NET 2002 o compilador empaquetado de .NET
Framework 1.0
Nullable
La opción Nullable le permite especificar el contexto que admite un valor NULL. Se
puede establecer en la configuración del proyecto mediante la etiqueta <Nullable> :
XML
<Nullable>enable</Nullable>
7 Nota
El análisis de flujo se utiliza para inferir la nulabilidad de las variables del código
ejecutable. La nulabilidad inferida de una variable es independiente de la nulabilidad
declarada de dicha variable. Las llamadas de método se analizan incluso cuando se
omiten de forma condicional. Es el caso de Debug.Assert en modo de versión.
) Importante
El contexto global que admite un valor NULL no se aplica a los archivos de código
generado. Con independencia de este valor, el contexto que admite un valor NULL
está deshabilitado para cualquier archivo de código fuente marcado como
generado. Hay cuatro maneras de marcar un archivo como generado:
DocumentationFile
La opción DocumentationFile permite insertar comentarios de documentación en un
archivo XML. Para obtener más información sobre cómo documentar el código, vea
Etiquetas recomendadas para comentarios de documentación. El valor especifica la ruta
al archivo XML de salida. El archivo XML contiene los comentarios en los archivos de
código fuente de la compilación.
XML
<DocumentationFile>path/to/file.xml</DocumentationFile>
7 Nota
Esta opción se puede usar en cualquier proyecto de estilo SDK de .NET. Para más
información, consulte la propiedad DocumentationFile.
OutputAssembly
La opción OutputAssembly especifica el nombre del archivo de salida. En la ruta de
salida se especifica la carpeta donde se coloca la salida del compilador.
XML
<OutputAssembly>folder</OutputAssembly>
Hay que especificar el nombre completo y la extensión del archivo que se quiere crear.
Si no especifica el nombre del archivo de salida, MSBuild usa el nombre del proyecto
para especificar el nombre del ensamblado de salida. Los proyectos de estilo antiguo
usan las reglas siguientes:
Un archivo .exe toma el nombre del archivo de código fuente que contiene el
método Main o instrucciones de nivel superior.
Un archivo .dll o .netmodule toma el nombre del primer archivo de código fuente.
Todos los módulos que se produzcan como parte de una compilación se convierten en
archivos asociados a cualquier ensamblado que también se haya producido en la
compilación. Use ildasm.exe para ver el manifiesto del ensamblado y los archivos
asociados.
Es obligatorio usar la opción del compilador OutputAssembly para que un archivo exe
sea el destino de un ensamblado de confianza.
PlatformTarget
Especifica qué versión de CLR puede ejecutar el ensamblado.
XML
<PlatformTarget>anycpu</PlatformTarget>
ProduceReferenceAssembly
La opción ProduceReferenceAssembly controla si el compilador genera ensamblados
de referencia.
XML
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
Los ensamblados de referencia son un tipo especial de ensamblado que contiene solo la
cantidad mínima de metadatos necesarios para representar la superficie de la API
pública de la biblioteca. Incluyen declaraciones para todos los miembros importantes al
hacer referencia a un ensamblado en las herramientas de compilación. Los ensamblados
de referencia excluyen todas las implementaciones de miembros y declaraciones de
miembros privados. Esos miembros no tienen ningún impacto observable en su
contrato de API. Para obtener más información, vea Ensamblados de referencia en la
Guía de .NET.
TargetType
La opción del compilador TargetType se puede especificar en uno de los formatos
siguientes:
library: para crear una biblioteca de código. library es el valor predeterminado.
exe: para crear un archivo .exe.
module para crear un módulo.
winexe para crear un programa de Windows.
winmdobj para crear un archivo .winmdobj intermedio.
appcontainerexe para crear un archivo .exe para aplicaciones Windows 8.x de
Microsoft Store.
7 Nota
XML
<TargetType>library</TargetType>
Si crea un ensamblado, puede indicar que todo o parte del código es conforme a CLS
mediante el atributo CLSCompliantAttribute.
biblioteca
La opción library hace que el compilador cree una biblioteca de vínculos dinámicos
(DLL) en lugar de un archivo ejecutable (EXE). El archivo DLL se creará con la extensión
.dll. A menos que se especifique lo contrario con la opción OutputAssembly, el archivo
de salida adopta el nombre del primer archivo de entrada. Al compilar un archivo .dll, no
es necesario un método Main.
exe
La opción exe hace que el compilador cree una aplicación de consola ejecutable (EXE). El
archivo ejecutable se creará con la extensión .exe. Use winexe para crear un archivo
ejecutable de un programa de Windows. A menos que se especifique de otro modo con
la opción OutputAssembly, el nombre del archivo de salida toma el del archivo de
entrada que contiene el punto de entrada (el método Main o instrucciones de nivel
superior). Solo se necesita un punto de entrada en los archivos de código fuente que se
compilan en un archivo .exe. La opción del compilador StartupObject le permite
especificar qué clase contiene el método Main , en aquellos casos en los que el código
tenga más de una clase con un método Main .
module
Esta opción hace que el compilador no genere un manifiesto del ensamblado. De forma
predeterminada, el archivo de salida creado al realizar la compilación con esta opción
tendrá una extensión .netmodule. El entorno de ejecución de .NET no puede cargar un
archivo que no tiene un manifiesto del ensamblado. Pero este archivo se puede
incorporar en el manifiesto del ensamblado con AddModules. Si se crea más de un
módulo en una única compilación, los tipos internal que haya en un módulo estarán
disponibles para otros módulos de la compilación. Cuando el código de un módulo
hace referencia a tipos internal de otro, se deben incorporar los dos módulos en un
manifiesto del ensamblado, mediante AddModules. No se admite la creación de un
módulo en el entorno de desarrollo de Visual Studio.
winexe
La opción winexe hace que el compilador cree un programa de Windows ejecutable
(EXE). El archivo ejecutable se creará con la extensión .exe. Un programa de Windows es
el que ofrece una interfaz de usuario de la biblioteca de .NET o con las API Windows.
Use exe para crear una aplicación de consola. A menos que se especifique de otro
modo con la opción OutputAssembly, el nombre del archivo de salida toma el nombre
del archivo de entrada que contiene el método Main. Solo se necesita un método Main
en los archivos de código fuente que se compilan en un archivo .exe. La opción
StartupObject le permite especificar qué clase contiene el método Main , en aquellos
casos en los que el código tenga más de una clase con un método Main .
winmdobj
Si usa la opción winmdobj, el compilador crea un archivo .winmdobj intermedio que se
puede convertir en un archivo binario de Windows Runtime ( .winmd). Después, el
archivo .winmd se puede usar en programas de JavaScript y C++, además de programas
de lenguajes administrados.
El valor winmdobj indica al compilador que un módulo intermedio es obligatorio.
Después, el archivo .winmdobj se puede proporcionar por medio de la herramienta de
exportación WinMDExp para generar un archivo de metadatos de Windows ( .winmd). El
archivo .winmd contiene el código de la biblioteca original y los metadatos de WinMD
que usan JavaScript o C++, y Windows Runtime. La salida de un archivo compilado
mediante la opción del compilador winmdobj solo se usa como entrada de la
herramienta de exportación WimMDExp. No se hace referencia directamente al archivo
.winmdobj. A menos que use la opción OutputAssembly, el nombre del archivo de
salida tomar el del primer archivo de entrada. No se necesita un método Main.
appcontainerexe
Si usa la opción del compilador appcontainerexe, este crea un archivo ejecutable de
Windows ( .exe) que se debe ejecutar en un contenedor de la aplicación. Esta opción
equivale a -target:winexe, pero está diseñada para las aplicaciones de la Tienda
Windows 8.x.
Las opciones siguientes controlan las entradas del compilador. La nueva sintaxis de
MSBuild se muestra en negrita. La sintaxis de csc.exe anterior se muestra en code style .
Referencias
La opción References hace que el compilador importe información de tipo public del
archivo especificado al proyecto actual, lo que permite hacer referencia a metadatos de
los archivos de ensamblado especificados.
XML
XML
<Reference Include="filename.dll">
<Aliases>LS</Aliases>
</Reference>
7 Nota
AddModules
Esta opción agrega un módulo que se ha creado con el modificador
<TargetType>module</TargetType> para la compilación actual:
XML
Donde file , file2 son archivos de salida que contienen metadatos. El archivo no
puede contener un manifiesto del ensamblado. Para importar más de un archivo, hay
que separar los nombres de archivo con una coma o un punto y coma. Todos los
módulos agregados con AddModules deben encontrarse en el mismo directorio que el
archivo de salida en tiempo de ejecución. Es decir, puede especificar un módulo de
cualquier directorio en el momento de la compilación, pero el módulo debe encontrarse
en el directorio de la aplicación en tiempo de ejecución. Si el módulo no se encuentra
en el directorio de la aplicación en tiempo de ejecución, obtendrá TypeLoadException.
file no puede contener ningún ensamblado. Por ejemplo, si el archivo de salida se ha
creado con la opción TargetType de module, sus metadatos se pueden importar con
AddModules.
XML
<References>
<EmbedInteropTypes>file1;file2;file3</EmbedInteropTypes>
</References>
7 Nota
Cuando se crea una instancia de un tipo COM incrustado en el código, hay que
crear la instancia mediante la interfaz adecuada. Si se intenta crear una instancia de
un tipo COM incrustado mediante la coclase, se produce un error.
Como sucede con la opción del compilador References, la opción del compilador
EmbedInteropTypes usa el archivo de respuesta Csc.rsp, que hace referencia a
ensamblados de .NET usados con frecuencia. Use la opción del compilador NoConfig si
no quiere que el compilador utilice el archivo Csc.rsp.
C#
ISampleInterface<SampleType> sample;
Los tipos que tienen un parámetro genérico cuyo tipo se ha incrustado desde un
ensamblado de interoperabilidad no se pueden usar si ese tipo pertenece a un
ensamblado externo. Esta restricción no se aplica a las interfaces. Por ejemplo, considere
la interfaz Range que se define en el ensamblado Microsoft.Office.Interop.Excel. Si una
biblioteca inserta tipos de interoperabilidad desde el ensamblado
Microsoft.Office.Interop.Excel y expone un método que devuelve un tipo genérico que
tiene un parámetro cuyo tipo es la interfaz Range, ese método debe devolver una
interfaz genérica, como se muestra en el ejemplo de código siguiente.
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Office.Interop.Excel;
return null;
return null;
C#
Las opciones siguientes controlan cómo el compilador notifica los errores y las
advertencias. La nueva sintaxis de MSBuild se muestra en negrita. La sintaxis de csc.exe
anterior se muestra en code style .
WarningLevel
La opción WarningLevel especifica el nivel de advertencia que debe mostrar el
compilador.
XML
<WarningLevel>3</WarningLevel>
El valor del elemento es el nivel de advertencia que quiere que se muestre para la
compilación: los números más bajos muestran solo advertencias de gravedad alta. Los
números más altos muestran más advertencias. El valor debe ser cero o un entero
positivo:
Nivel de Significado
advertencia
2 Advertencia
La línea de comandos del compilador acepta valores mayores que 4 para permitir
advertencias de ola de advertencias. Sin embargo, el SDK de .NET establece
WarningLevel para que coincida con AnalysisLevel en el archivo del proyecto.
Nivel de análisis
Nivel de análisis Significado
más reciente (valor Muestra todas las advertencias informativas hasta la versión actual
predeterminado) (esta inclusive).
Para obtener información sobre un error o advertencia, puede buscar el código de error
en el Índice de la Ayuda. Para conocer otras maneras de obtener información sobre un
error o advertencia, vea Errores del compilador de C#. Use TreatWarningsAsErrors para
tratar todas las advertencias como errores. Use NoWarn para deshabilitar ciertas
advertencias.
TreatWarningsAsErrors
La opción TreatWarningsAsErrors trata todas las advertencias como errores. También
puede usar TreatWarningsAsErrors para establecer solo algunas advertencias como
errores. Si activa TreatWarningsAsErrors, puede usar WarningsNotAsErrors para
enumerar las advertencias que no se deben tratar como errores.
XML
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
) Importante
XML
<WarningsAsErrors>0219,CS0168</WarningsAsErrors>
XML
<WarningsNotAsErrors>0219,CS0168</WarningsNotAsErrors>
NoWarn
La opción NoWarn permite suprimir el compilador para mostrar una o varias
advertencias. Separe varios números de advertencia con una coma.
XML
<NoWarn>number1, number2</NoWarn>
number1 , number2 Números de advertencia que quiere que el compilador suprima. Debe
CodeAnalysisRuleSet
Especifica un archivo de conjunto de reglas que configura diagnósticos específicos.
XML
<CodeAnalysisRuleSet>MyConfiguration.ruleset</CodeAnalysisRuleSet>
ErrorLog
Especifique un archivo para registrar todos los diagnósticos del compilador y el
analizador.
XML
<ErrorLog>compiler-diagnostics.sarif</ErrorLog>
XML
<ErrorLog>logVersion21.json,version=2.1</ErrorLog>
El separador puede ser una coma ( , ) o un punto y coma ( ; ). Los valores válidos para la
versión son: "1", "2" y "2.1". El valor predeterminado es "1". "2" y "2.1" significan la
versión 2.1.0 de SARIF.
ReportAnalyzer
Notifica información adicional del analizador, como el tiempo de ejecución.
XML
<ReportAnalyzer>true</ReportAnalyzer>
) Importante
DebugType
La opción DebugType hace que el compilador genere información de depuración y la
incluya en el archivo o los archivos de salida. La información de depuración se ha
agregado de forma predeterminada.
XML
<DebugType>pdbonly</DebugType>
En todas las versiones del compilador a partir de C# 6.0, no hay ninguna diferencia entre
pdbonly y full. Elija pdbonly. Para cambiar la ubicación del archivo .pdb, vea PdbFile.
Valor Significado
pdbonly Igual a full . Vea la nota siguiente para obtener más información.
) Importante
Optimización
La opción Optimización habilita o deshabilita las optimizaciones realizadas por el
compilador para que el archivo de salida sea menor, más rápido y más eficaz. La opción
Optimización está habilitada de forma predeterminada para una configuración de
compilación de versión. Está desactivada de forma predeterminada para una
configuración de compilación de depuración.
XML
<Optimize>true</Optimize>
Determinista
Hace que el compilador genere un ensamblado cuya salida byte a byte es idéntica en
todas las compilaciones para las entradas idénticas.
XML
<Deterministic>true</Deterministic>
idéntico en todas las compilaciones, siempre y cuando la entrada siga siendo la misma.
En este tipo de compilación, los campos timestamp y MVID se reemplazarán por los
valores derivados de un hash de todas las entradas de la compilación. El compilador
tiene en cuenta las siguientes entradas que afectan al determinismo:
ProduceOnlyReferenceAssembly
La opción ProduceOnlyReferenceAssembly indica que un ensamblado de referencia se
debe mostrar en lugar de un ensamblado de implementación, como el resultado
principal. El parámetro ProduceOnlyReferenceAssembly deshabilita de forma
automática la generación de archivos PDB, ya que los ensamblados de referencia no se
pueden ejecutar.
XML
<ProduceOnlyReferenceAssembly>true</ProduceOnlyReferenceAssembly>
Las opciones siguientes controlan las opciones de seguridad del compilador. La nueva
sintaxis de MSBuild se muestra en negrita. La sintaxis de csc.exe anterior se muestra en
code style .
PublicSign
Esta opción hace que el compilador aplique una clave pública pero no firma el
ensamblado. La opción PublicSign también establece un bit en el ensamblado que
indica al entorno de ejecución que el archivo está firmado.
XML
<PublicSign>true</PublicSign>
XML
<DelaySign>true</DelaySign>
KeyFile
Especifica el nombre de archivo que contiene la clave criptográfica.
XML
<KeyFile>filename</KeyFile>
file es el nombre del archivo que contiene la clave de nombre seguro. Cuando se usa
esta opción, el compilador inserta la clave pública del archivo especificado en el
manifiesto del ensamblado y, después, firma el último ensamblado con la clave privada.
Para generar un archivo de claves, escriba sn -k file en la línea de comandos. Si se
compila con -target:module, el nombre del archivo de claves se mantiene en el módulo
y se incorpora en el ensamblado que se crea al compilarlo con AddModules. También
puede pasar la información de cifrado al compilador con KeyContainer. Use DelaySign
si quiere firmar el ensamblado de forma parcial. Si se especifica tanto KeyFile como
KeyContainer en la misma compilación, el compilador probará primero el contenedor
de claves. Si lo consigue, el ensamblado se firma con la información del contenedor de
claves. Si el compilador no encuentra el contenedor de claves, probará el archivo
especificado con KeyFile. Si la operación es correcta, el ensamblado se firma con la
información del archivo de clave y la información de la clave se instalará en el
contenedor de claves. En la siguiente compilación, el contenedor de claves será válido.
Es posible que un archivo de clave solo contenga la clave pública. Para obtener más
información, vea Crear y usar ensamblados con nombre seguro y Retraso de la firma de
un ensamblado.
KeyContainer
Especifica el nombre del contenedor de claves criptográficas.
XML
<KeyContainer>container</KeyContainer>
HighEntropyVA
La opción del compilador HighEntropyVA indica al kernel de Windows si un archivo
ejecutable determinado admite la selección aleatoria del diseño del espacio de
direcciones (ASLR) de alta entropía.
XML
<HighEntropyVA>true</HighEntropyVA>
Win32Resource
La opción Win32Resource inserta un recurso de Win32 en el archivo de salida.
XML
<Win32Resource>filename</Win32Resource>
Win32Icon
La opción Win32Icon inserta un archivo .ico en el archivo de salida, lo que proporciona
al archivo de salida la apariencia esperada en el Explorador de archivos.
XML
<Win32Icon>filename</Win32Icon>
filename es el archivo .ico que quiere agregar al archivo de salida. Se puede crear un
Win32Manifest
Use la opción Win32Manifest para especificar un archivo de manifiesto de aplicación
Win32 definido por el usuario que se va a insertar en un archivo portable ejecutable (PE)
del proyecto.
XML
<Win32Manifest>filename</Win32Manifest>
7 Nota
NoWin32Manifest
Use la opción NoWin32Manifest para indicar al compilador que no inserte ningún
manifiesto de aplicación en el archivo ejecutable.
XML
<NoWin32Manifest />
Recursos
Inserta el recurso especificado en el archivo de salida.
XML
<Resources Include=filename>
<LogicalName>identifier</LogicalName>
<Access>accessibility-modifier</Access>
</Resources>
identifier (opcional) es el nombre lógico del recurso, que se usa para cargarlo. El valor
predeterminado es el nombre del archivo. accessibility-modifier (opcional) es la
accesibilidad del recurso: pública o privada. El valor predeterminado es public. De
manera predeterminada, los recursos son públicos en el ensamblado cuando se crean
mediante el compilador de C#. Para que sean privados, especifique el modificador de
accesibilidad private . No se permite ninguna otra accesibilidad distinta de public o
private . Si filename es un archivo de recursos de .NET creado, por ejemplo, con
LinkResources
Crea un vínculo a un recurso de .NET en el archivo de salida. El archivo de recursos no se
agrega al archivo de salida. LinkResources difiere de la opción Resource, que sí inserta
un archivo de recursos en el archivo de salida.
XML
<LinkResources Include=filename>
<LogicalName>identifier</LogicalName>
<Access>accessibility-modifier</Access>
</LinkResources>
filename es el archivo de recursos de .NET con el que quiere crear un vínculo desde el
ensamblado. identifier (opcional) es el nombre lógico del recurso, que se usa para
cargarlo. El valor predeterminado es el nombre del archivo. accessibility-modifier
(opcional) es la accesibilidad del recurso: pública o privada. El valor predeterminado es
public. De manera predeterminada, los recursos vinculados son públicos en el
ensamblado cuando se crean con el compilador de C#. Para que sean privados,
especifique el modificador de accesibilidad private . No se permite ningún otro
modificador distinto de public o private . Si filename es un archivo de recursos de
.NET creado, por ejemplo, con Resgen.exe o en el entorno de desarrollo, se puede
acceder a él con miembros del espacio de nombres System.Resources. Para obtener más
información, vea System.Resources.ResourceManager. Para todos los demás recursos,
use los métodos GetManifestResource de la clase Assembly para tener acceso al recurso
en tiempo de ejecución. El archivo especificado en filename puede tener cualquier
formato. Por ejemplo, se puede hacer que una DLL nativa forme parte de un
ensamblado para que se pueda instalar en la caché global de ensamblados y sea
accesible desde código administrado del ensamblado. También es posible realizar lo
mismo en Assembly Linker. Para obtener más información, vea Al.exe (Assembly Linker)
y Trabajar con ensamblados y la memoria caché global de ensamblados.
Otras opciones del compilador de C#
Artículo • 15/02/2023 • Tiempo de lectura: 2 minutos
ResponseFiles
La opción ResponseFiles le permite especificar un archivo que contiene opciones del
compilador y archivos de código fuente para compilar.
XML
<ResponseFiles>response_file</ResponseFiles>
Consola
NoLogo
La opción NoLogo suprime la presentación del banner de inicio de sesión cuando se
inicia el compilador y muestra mensajes informativos durante la compilación.
XML
<NoLogo>true</NoLogo>
NoConfig
La opción NoConfig indica al compilador que no realice la compilación con el archivo
csc.rsp.
XML
<NoConfig>true</NoConfig>
XML
<StartupObject>MyNamespace.Program</StartupObject>
or
XML
<MainEntryPoint>MyNamespace.Program</MainEntryPoint>
7 Nota
Esta opción no se puede usar para un proyecto que incluya instrucciones de nivel
superior, incluso si contiene uno o más métodos Main .
PdbFile
La opción del compilador PdbFile especifica el nombre y la ubicación del archivo de
símbolos de depuración. El valor filename especifica el nombre y la ubicación del
archivo de símbolos de depuración.
XML
<PdbFile>filename</PdbFile>
PathMap
La opción del compilador PathMap especifica cómo asignar rutas de acceso físicas a los
nombres de rutas de acceso de origen generados por el compilador. Esta opción asigna
cada ruta de acceso física en la máquina donde el compilador se ejecuta a una ruta de
acceso correspondiente que debe escribirse en los archivos de salida. En el ejemplo
siguiente, path1 es la ruta completa a los archivos de código fuente en el entorno actual
y sourcePath1 es la ruta de origen sustituida por path1 en cualquier archivo de salida.
Para especificar varias rutas de acceso de origen asignadas, sepárelas con una coma.
XML
<PathMap>path1=sourcePath1,path2=sourcePath2</PathMap>
El compilador escribe la ruta de acceso de origen en la salida por las razones siguientes:
ApplicationConfiguration
La opción del compilador ApplicationConfiguration permite a una aplicación de C#
especificar la ubicación del archivo de configuración de la aplicación de un ensamblado
(app.config) al Common Language Runtime (CLR) en tiempo de enlace del ensamblado.
XML
<ApplicationConfiguration>file</ApplicationConfiguration>
XML
7 Nota
archivo.
XML
<configuration>
<runtime>
<assemblyBinding>
</assemblyBinding>
</runtime>
</configuration>
AdditionalLibPaths
La opción AdditionalLibPaths especifica la ubicación de los ensamblados a los que se
hace referencia con la opción References.
XML
<AdditionalLibPaths>dir1[,dir2]</AdditionalLibPaths>
ensamblados. Separe los nombres de directorio con una coma y sin espacio en blanco
entre ellos. El compilador busca referencias a ensamblados que no presentan la ruta
completa en el siguiente orden:
GenerateFullPaths
La opción GenerateFullPaths hace que el compilador especifique la ruta completa al
archivo cuando se muestran los errores de compilación y las advertencias.
Xml
<GenerateFullPaths>true</GenerateFullPaths>
PreferredUILang
Mediante la opción del compilador PreferredUILang puede especificar el idioma en el
que el compilador de C# muestra el resultado, como los mensajes de error.
XML
<PreferredUILang>language</PreferredUILang>
language es el nombre del idioma que se va a usar para la salida del compilador. Puede
usar la opción del compilador PreferredUILang para especificar el idioma que quiere
que use el compilador de C# para los mensajes de error y otra salida de la línea de
comandos. Si el paquete de idioma para el idioma no está instalado, en su lugar se usa
la configuración de idioma del sistema operativo.
BaseAddress
La opción BaseAddress permite especificar la dirección base preferida en la que cargar
un archivo DLL. Para obtener más información sobre cuándo y por qué usar esta opción,
vea el blog de Larry Osterman.
XML
<BaseAddress>address</BaseAddress>
address es la dirección base del archivo DLL. Esta dirección puede especificarse como
un número octal, hexadecimal o decimal. La dirección base predeterminada para un
archivo DLL se establece mediante Common Language Runtime de .NET. La palabra de
orden inferior en esta dirección se redondeará. Por ejemplo, si especifica 0x11110001 , se
redondeará a 0x11110000 . Para completar el proceso de firma para un archivo DLL, use
SN.EXE con la opción -R.
ChecksumAlgorithm
Esta opción controla el algoritmo de suma de comprobación que se usa para codificar
los archivos de código fuente en el archivo PDB.
XML
<ChecksumAlgorithm>algorithm</ChecksumAlgorithm>
CodePage
Esta opción especifica qué página de códigos se va a usar durante la compilación si la
página necesaria no es la página de códigos predeterminada actual del sistema.
XML
<CodePage>id</CodePage>
id es el id. de la página de códigos que se va a usar para todos los archivos de código
Utf8Output
La opción Utf8Output muestra los resultados del compilador en codificación UTF-8.
XML
<Utf8Output>true</Utf8Output>
FileAlignment
La opción FileAlignment permite especificar el tamaño de las secciones en el archivo de
salida. Los valores válidos son 512, 1024, 2048, 4096 y 8192. Estos valores están en
bytes.
XML
<FileAlignment>number</FileAlignment>
ErrorEndLocation
Indica al compilador que muestre la línea y la columna de salida de la ubicación final de
cada error.
XML
<ErrorEndLocation>true</ErrorEndLocation>
NoStandardLib
NoStandardLib impide la importación de mscorlib.dll, que define el espacio de nombres
System completo.
XML
<NoStandardLib>true</NoStandardLib>
Use esta opción si desea definir o crear sus propios objetos y espacio de nombres
System. Si no se especifica NoStandardLib, mscorlib.dll se importa en el programa
(equivale a especificar <NoStandardLib>false</NoStandardLib> ).
SubsystemVersion
Especifica la versión mínima del subsistema en el que se ejecuta el archivo ejecutable.
Normalmente, esta opción garantiza que el archivo ejecutable pueda usar características
de seguridad que no están disponibles en versiones anteriores de Windows.
7 Nota
XML
<SubsystemVersion>major.minor</SubsystemVersion>
Windows 7 6.01
Windows 8 6.02
ModuleAssemblyName
Especifica el nombre de un ensamblado con tipos no públicos a los que puede acceder
un archivo .netmodule.
XML
<ModuleAssemblyName>assembly_name</ModuleAssemblyName>
Para obtener más información sobre cómo crear un archivo .netmodule, vea la opción
TargetType de module. Para obtener más información sobre los ensamblados de
confianza, vea Ensamblados de confianza.
Comentarios de la documentación XML
Artículo • 15/02/2023 • Tiempo de lectura: 12 minutos
C#
/// <summary>
/// </summary>
Delimitador de una sola línea /// : los ejemplos de documentación y las plantillas
de proyecto de C# usan este formulario. Si hay un espacio en blanco después del
delimitador, no se incluye en la salida XML.
7 Nota
Para las líneas que aparecen después de la que comienza con el delimitador
/** , el compilador busca un patrón común al principio de cada línea. El patrón
puede consistir en un espacio en blanco opcional y un asterisco ( * ), seguido de
otro espacio en blanco opcional. Si el compilador encuentra un patrón común al
principio de cada línea que no comienza con el delimitador /** ni termina con
el delimitador */ , omite ese patrón para cada línea.
C#
/** <summary>text</summary> */
/**
<summary>text</summary>
*/
/**
* <summary>text</summary>
*/
C#
/**
* <summary>
* text </summary>*/
C#
/**
* <summary>
text </summary>
*/
/**
* <summary>
* text
* text2
* </summary>
*/
Para hacer referencia a elementos XML (por ejemplo, la función procesa los elementos
XML concretos que desea describir en un comentario de documentación XML), puede
usar el mecanismo de entrecomillado estándar ( < y > ). Para hacer referencia a
identificadores genéricos en elementos de referencia de código ( cref ), puede usar los
caracteres de escape (por ejemplo, cref="List<T>" ) o llaves ( cref="List{T}" ).
Como caso especial, el compilador analiza las llaves como corchetes angulares para que
la creación del comentario de documentación resulte menos complicada al hacer
referencia a identificadores genéricos.
7 Nota
Cadenas de identificador
Cada tipo o miembro se almacena en un elemento del archivo XML de salida. Cada uno
de esos elementos tiene una cadena de identificador única que identifica el tipo o
miembro. La cadena de identificador debe tener en cuenta los operadores, los
parámetros, los valores devueltos, los parámetros de tipo genérico y los parámetros
ref , in y out . Para codificar todos esos elementos potenciales, el compilador sigue
reglas claramente definidas para generar las cadenas de identificador. Los programas
que procesan el archivo XML usan la cadena de identificador para identificar el
elemento de reflexión o de metadatos correspondiente de .NET al que se aplica la
documentación.
El compilador cumple las siguientes reglas cuando genera las cadenas de identificador:
F campo
E event
Para tipos genéricos, el nombre del tipo está seguido de una tilde aguda y después
de un número que indica el número de parámetros de tipo genérico. Por ejemplo:
<member name="T:SampleClass``2"> es la etiqueta de un tipo que se define como
C#
namespace MyNamespace
/// <summary>
/// </summary>
/// <summary>
/// </summary>
public MyClass() { }
/// <summary>
/// </summary>
public MyClass(int i) { }
/// <summary>
/// </summary>
/// <summary>
/// </summary>
/// <summary>
/// </summary>
/// <summary>
/// </summary>
public int someMethod(string str, ref int nm, void* ptr) { return 1;
}
/// <summary>
/// </summary>
/// <summary>
/// </summary>
/// <summary>
/// </summary>
/// <summary>
/// </summary>
/// <summary>
/// </summary>
/// <returns></returns>
/// <summary>
/// <summary>
/// </summary>
/// <summary>
/// </summary>
La documentación XML empieza con /// . Cuando se crea un proyecto, las plantillas
agregan automáticamente unas líneas de inicio /// . El procesamiento de estos
comentarios tiene algunas restricciones:
7 Nota
Etiquetas generales usadas para varios elementos: estas etiquetas son el conjunto
mínimo de cualquier API.
<summary>: el valor de este elemento se muestra en IntelliSense en
Visual Studio.
<remarks> **
Etiquetas usadas en miembros: estas etiquetas se usan al documentar métodos y
propiedades.
<returns>: el valor de este elemento se muestra en IntelliSense en Visual Studio.
<param> *: el valor de este elemento se muestra en IntelliSense en
Visual Studio.
<paramref>
<exception> *
<value>: el valor de este elemento se muestra en IntelliSense en Visual Studio.
Formato de salida de documentación: estas etiquetas proporcionan instrucciones
de formato para las herramientas que generan documentación.
<para>
<list>
<c>
<code>
<example> **
Reutilización de texto de documentación: estas etiquetas proporcionan
herramientas que facilitan la reutilización de comentarios XML.
<inheritdoc> **
<include> *
Generación de vínculos y referencias: estas etiquetas generan vínculos a otra
documentación.
<see> *
<seealso> *
cref
href
Etiquetas para métodos y tipos genéricos: estas etiquetas solo se usan en métodos
y tipos genéricos.
<typeparam> *: el valor de este elemento se muestra en IntelliSense en
Visual Studio.
<typeparamref>
7 Nota
C#
/// <summary>
/// </summary>
Etiquetas generales
<summary>
XML
<summary>description</summary>
La etiqueta <summary> debe usarse para describir un tipo o un miembro de tipo. Use
<remarks> para agregar información adicional a una descripción de tipo. Use el atributo
cref para permitir que herramientas de documentación como DocFX y Sandcastle
creen hipervínculos internos a las páginas de documentación de los elementos de
código. El texto de la etiqueta <summary> es la única fuente de información sobre el tipo
en IntelliSense, y también se muestra en la ventana Examinador de objetos.
<remarks>
XML
<remarks>
description
</remarks>
<returns>
XML
<returns>description</returns>
<param>
XML
<param name="name">description</param>
dobles (" "). Los nombres de los parámetros deben coincidir con la firma de la API.
Si uno o varios parámetros no aparecen, el compilador emite una advertencia. El
compilador también emite una advertencia si el valor de name no coincide con un
parámetro formal de la declaración del método.
<paramref>
XML
<paramref name="name"/>
name : nombre del parámetro al que se hace referencia. Ponga el nombre entre
La etiqueta <paramref> ofrece una manera de indicar que una palabra en los
comentarios del código (por ejemplo, en un bloque <summary> o <remarks> ) hace
referencia a un parámetro. El archivo XML se puede procesar para dar formato a esta
palabra de alguna manera distinta, por ejemplo, con una fuente en negrita o cursiva.
<exception>
XML
<exception cref="member">description</exception>
cref = " member ": referencia a una excepción que está disponible desde el entorno
de compilación actual. El compilador comprueba si la excepción dada existe y
traduce member al nombre de elemento canónico en la salida XML. member debe
aparecer entre comillas dobles (" ").
<value>
XML
<value>property-description</value>
La etiqueta <value> le permite describir el valor que representa una propiedad. Cuando
agrega una propiedad mediante un asistente de código en el entorno de desarrollo
.NET de Visual Studio, se agrega una etiqueta <summary> para la nueva propiedad.
Debe agregar manualmente una etiqueta <value> para describir el valor que representa
la propiedad.
<para>
XML
<remarks>
<para>
</para>
<para>
</para>
</remarks>
<list>
XML
<list type="bullet|number|table">
<listheader>
<term>term</term>
<description>description</description>
</listheader>
<item>
<term>Assembly</term>
</item>
</list>
El bloque <listheader> se usa para definir la fila de encabezado de una tabla o de una
lista de definiciones. Cuando se define una tabla, solo es necesario proporcionar una
entrada para term en el encabezado. Cada elemento de la lista se especifica con un
bloque <item> . Cuando se crea una lista de definiciones, es necesario especificar tanto
term como description . En cambio, para una tabla, lista con viñetas o lista numerada,
solo es necesario suministrar una entrada para description . Una lista o una tabla
pueden tener tantos bloques <item> como sean necesarios.
<c>
XML
<c>text</c>
La etiqueta <c> le proporciona una manera de indicar que el texto dentro de una
descripción debe marcarse como código. Use <code> para indicar varias líneas como
código.
<code>
XML
<code>
var index = 5;
index++;
</code>
La etiqueta <code> se usa para indicar varias líneas de código. Use <c> para indicar que
el texto de línea única dentro de una descripción debe marcarse como código.
<example>
XML
<example>
<code>
var index = 5;
index++;
</code>
</example>
<inheritdoc>
XML
Puede usar este atributo para filtrar las etiquetas que se van a incluir o excluir en la
documentación heredada.
Agregue los comentarios XML en las clases base o las interfaces y deje que inheritdoc
copie los comentarios en las clases en implementación. Agregue los comentarios XML a
los métodos sincrónicos y deje que inheritdoc copie los comentarios en las versiones
asincrónicas de los mismos métodos. Si quiere copiar los comentarios de un miembro
concreto, puede usar el atributo cref para especificar el miembro.
<include>
XML
<see>
XML
<see cref="member"/>
<!-- or -->
<!-- or -->
<!-- or -->
<see langword="keyword"/>
La etiqueta <see> permite especificar un vínculo desde el texto. Use <seealso> para
indicar que el texto debe colocarse en una sección Consulte también. Use el atributo
cref para crear hipervínculos internos a las páginas de documentación de los elementos
de código. Incluya los parámetros de tipo para especificar una referencia a un tipo
genérico o método, como cref="IDictionary{T, U}" . Además, href es un atributo
válido que actúa como un hipervínculo.
<seealso>
XML
<seealso cref="member"/>
<!-- or -->
que se puede hacer clic, con texto GitHub que vincula a https://github.com .
La etiqueta <seealso> permite especificar el texto que quiere que aparezca en una
sección Vea también. Use <see> para especificar un vínculo desde dentro del texto. No
se puede anidar la etiqueta seealso dentro de la etiqueta summary .
Atributo cref
El atributo cref de una etiqueta de documentación XML significa "referencia de
código". Especifica que el texto interno de la etiqueta es un elemento de código, como
un tipo, un método o una propiedad. En herramientas de documentación como
DocFX y Sandcastle , use los atributos cref para generar hipervínculos a la página
donde se documenta el tipo o miembro de manera automática.
Atributo href
El atributo href significa una referencia a una página web. Puede usarlo para hacer
referencia directamente a la documentación en línea sobre la API o la biblioteca.
<typeparam>
XML
TResult : nombre del parámetro de tipo. Ponga el nombre entre comillas dobles ("
").
<typeparamref>
XML
<typeparamref name="TKey"/>
TKey : nombre del parámetro de tipo. Ponga el nombre entre comillas dobles (" ").
Use esta etiqueta para permitir que los consumidores del archivo de documentación
den formato a la palabra de alguna manera distinta, por ejemplo en cursiva.
Este artículo contiene tres ejemplos para agregar comentarios de documentación XML a
la mayoría de los elementos de lenguaje de C#. En el primer ejemplo se muestra cómo
documentar una clase con miembros diferentes. En el segundo se muestra cómo
reutilizar las explicaciones de una jerarquía de clases o interfaces. En el tercero se
muestran las etiquetas que se van a usar para las clases y los miembros genéricos. En el
segundo y tercer ejemplo se usan conceptos que se tratan en el primero.
C#
/// <summary>
/// </summary>
/// <remarks>
/// <para>
/// </para>
/// <item>
/// <term>Summary</term>
/// <description>
/// This should provide a one sentence summary of the class or member.
/// </description>
/// </item>
/// <item>
/// <term>Remarks</term>
/// <description>
/// </description>
/// </item>
/// <item>
/// <term>para</term>
/// <description>
/// </description>
/// </item>
/// <item>
/// <term>list</term>
/// <description>
/// </description>
/// </item>
/// <item>
/// <description>
/// </description>
/// </item>
/// <item>
/// <term>value</term>
/// </item>
/// <item>
/// <term>exception</term>
/// <description>
/// </description>
/// </item>
/// <item>
/// <description>
/// </description>
/// </item>
/// <item>
/// <description>
/// </description>
/// </item>
/// </list>
/// <para>
/// The list above uses the "table" style. You could
/// <br/>
/// </para>
/// </remarks>
/// <value>
/// </value>
/// <remarks>
/// <para>
/// </para>
/// </remarks>
get;
set;
/// <summary>
/// </summary>
/// <returns>
/// </returns>
/// </param>
/// </param>
/// <example>
/// <code>
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
/// <see
href="https://docs.microsoft.com/dotnet/api/system.int32.maxvalue"/>
/// </exception>
/// <summary>
/// </summary>
/// <remarks>
/// </remarks>
/// </param>
/// </param>
C#
class Test
class Test2
XML
<MyDocs>
<MyMembers name="test">
<summary>
</summary>
</MyMembers>
<MyMembers name="test2">
<summary>
</summary>
</MyMembers>
</MyDocs>
C#
/// <summary>
/// </summary>
/// <remarks>
/// </remarks>
///<inheritdoc/>
/// <summary>
/// </summary>
/// <remarks>
/// </remarks>
/// <summary>
/// </summary>
/// <remarks>
/// </remarks>
///<inheritdoc cref="ITestInterface"/>
/// <summary>
/// This class shows hows you can "inherit" the doc
/// </summary>
/// <remarks>
/// </remarks>
/// <summary>
/// In this example, this summary is only visible for this method.
/// </summary>
/// <Summary>
/// <summary>
/// </summary>
/// <remarks>
/// </remarks>
Tipos genéricos
Use la etiqueta <typeparam> para describir los parámetros de tipo de tipos y métodos
genéricos. El valor del atributo cref exige nueva sintaxis para hacer referencia a una
clase o un método genéricos:
C#
/// <summary>
/// </summary>
/// <remarks>
/// In generic classes and methods, you'll often want to reference the
/// </remarks>
class GenericClass<T>
/// <Summary>
/// </Summary>
/// <summary>
/// </summary>
/// <remarks>
/// The parameter and return value are both of an arbitrary type,
/// </remarks>
return para;
C#
namespace TaggedLibrary
/*
*/
/// <summary>
/// <item>
/// <term>Add</term>
/// </item>
/// <item>
/// <term>Subtract</term>
/// <item>
/// <term>Multiply</term>
/// </item>
/// <item>
/// <term>Divide</term>
/// </item>
/// </list>
/// </summary>
/// <remarks>
/// <para>
/// </para>
/// <para>
/// </para>
/// </remarks>
/// <summary>
/// </summary>
/// <returns>
/// </returns>
/// <example>
/// <code>
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
/// </exception>
return a + b;
/// <summary>
/// </summary>
/// <returns>
/// </returns>
/// <example>
/// <code>
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
return a + b;
/// <summary>
/// </summary>
/// <returns>
/// </returns>
/// <example>
/// <code>
/// if (c > 1)
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
return a - b;
/// <summary>
/// </summary>
/// <returns>
/// </returns>
/// <example>
/// <code>
/// if (c > 1)
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
return a - b;
/// <summary>
/// </summary>
/// <returns>
/// </returns>
/// <example>
/// <code>
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
return a * b;
/// <summary>
/// </summary>
/// <returns>
/// </returns>
/// <example>
/// <code>
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
return a * b;
/// <summary>
/// </summary>
/// <returns>
/// </returns>
/// <example>
/// <code>
/// if (c > 1)
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
/// </exception>
return a / b;
/// <summary>
/// </summary>
/// <returns>
/// </returns>
/// <example>
/// <code>
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
/// </exception>
return a / b;
Es posible que los comentarios oculten el código. En el ejemplo final se muestra cómo
se adaptaría esta biblioteca para usar la etiqueta include . Mueva toda la
documentación a un archivo XML:
XML
<docs>
<members name="math">
<Math>
<summary>
</summary>
<remarks>
</remarks>
</Math>
<AddInt>
<summary>
</summary>
<returns>
</returns>
<example>
<code>
if (c > 10)
Console.WriteLine(c);
</code>
</example>
parameter is max
</AddInt>
<AddDouble>
<summary>
</summary>
<returns>
</returns>
<example>
<code>
if (c > 10)
Console.WriteLine(c);
</code>
</example>
</AddDouble>
<SubtractInt>
<summary>
</summary>
<returns>
</returns>
<example>
<code>
if (c > 1)
Console.WriteLine(c);
</code>
</example>
</SubtractInt>
<SubtractDouble>
<summary>
</summary>
<returns>
</returns>
<example>
<code>
if (c > 1)
Console.WriteLine(c);
</code>
</example>
</SubtractDouble>
<MultiplyInt>
<summary>
</summary>
<returns>
</returns>
<example>
<code>
if (c > 100)
Console.WriteLine(c);
</code>
</example>
</MultiplyInt>
<MultiplyDouble>
<summary>
</summary>
<returns>
<example>
<code>
if (c > 100.0)
Console.WriteLine(c);
</code>
</example>
</MultiplyDouble>
<DivideInt>
<summary>
</summary>
<returns>
</returns>
<example>
<code>
if (c > 1)
Console.WriteLine(c);
</code>
</example>
<exception cref="System.DivideByZeroException">
</exception>
</DivideInt>
<DivideDouble>
<summary>
</summary>
<returns>
</returns>
<example>
<code>
if (c > 1.0)
Console.WriteLine(c);
</code>
</example>
</DivideDouble>
</members>
</docs>
namespace IncludeTag
/*
*/
return a + b;
return a + b;
return a - b;
return a - b;
return a * b;
return a * b;
return a / b;
return a / b;
Algunos errores del compilador de C# tienen temas correspondientes que explican por
qué se genera el error y, en algunos casos, cómo corregirlo. Utilice uno de los siguientes
pasos para ver si la Ayuda está disponible para un mensaje de error concreto.
Si ninguno de estos pasos conduce a información sobre el error, vaya al final de esta
página y envíe comentarios con el número o el texto del error.
Para obtener información sobre cómo configurar las opciones de advertencia y error en
C#, vea Opciones del compilador de C# o Compilar (Página, Diseñador de proyectos)
(C#) en Visual Studio.
7 Nota
Vea también
Opciones del compilador de C#
Página Compilar (Diseñador de proyectos) (C#)
WarningLevel (opciones del compilador de C#)
NoWarn (opciones del compilador de C#)
Detailed table of contents
Artículo • 01/12/2022 • Tiempo de lectura: 12 minutos
Foreword
Introduction
§1 Scope
§2 Normative references
§3 Terms and definitions
§4 General description
§5 Conformance
§6 Lexical structure
§6.1 Programs
§6.2 Grammars
§6.2.1 General
§6.2.2 Grammar notation
§6.2.3 Lexical grammar
§6.2.4 Syntactic grammar
§6.2.5 Grammar ambiguities
§6.3 Lexical analysis
§6.3.1 General
§6.3.2 Line terminators
§6.3.3 Comments
§6.3.4 White space
§6.4 Tokens
§6.4.1 General
§6.4.2 Unicode character escape sequences
§6.4.3 Identifiers
§6.4.4 Keywords
§6.4.5 Literals
§6.4.5.1 General
§6.4.5.2 Boolean literals
§6.4.5.3 Integer literals
§6.4.5.4 Real literals
§6.4.5.5 Character literals
§6.4.5.6 String literals
§6.4.5.7 The null literal
§6.4.6 Operators and punctuators
§6.5 Pre-processing directives
§6.5.1 General
§6.5.2 Conditional compilation symbols
§6.5.3 Pre-processing expressions
§6.5.4 Definition directives
§6.5.5 Conditional compilation directives
§6.5.6 Diagnostic directives
§6.5.7 Region directives
§6.5.8 Line directives
§6.5.9 Pragma directives
§7 Basic concepts
§7.1 Application startup
§7.2 Application termination
§7.3 Declarations
§7.4 Members
§7.4.1 General
§7.4.2 Namespace members
§7.4.3 Struct members
§7.4.4 Enumeration members
§7.4.5 Class members
§7.4.6 Interface members
§7.4.7 Array members
§7.4.8 Delegate members
§7.5 Member access
§7.5.1 General
§7.5.2 Declared accessibility
§7.5.3 Accessibility domains
§7.5.4 Protected access
§7.5.5 Accessibility constraints
§7.6 Signatures and overloading
§7.7 Scopes
§7.7.1 General
§7.7.2 Name hiding
§7.7.2.1 General
§7.7.2.2 Hiding through nesting
§7.7.2.3 Hiding through inheritance
§7.8 Namespace and type names
§7.8.1 General
§7.8.2 Unqualified names
§7.8.3 Fully qualified names
§7.9 Automatic memory management
§7.10 Execution order
§8 Types
§8.1 General
§8.2 Reference types
§8.2.1 General
§8.2.2 Class types
§8.2.3 The object type
§8.2.4 The dynamic type
§8.2.5 The string type
§8.2.6 Interface types
§8.2.7 Array types
§8.2.8 Delegate types
§8.3 Value types
§8.3.1 General
§8.3.2 The System.ValueType type
§8.3.3 Default constructors
§8.3.4 Struct types
§8.3.5 Simple types
§8.3.6 Integral types
§8.3.7 Floating-point types
§8.3.8 The Decimal type
§8.3.9 The Bool type
§8.3.10 Enumeration types
§8.3.11 Nullable value types
§8.3.12 Boxing and unboxing
§8.4 Constructed types
§8.4.1 General
§8.4.2 Type arguments
§8.4.3 Open and closed types
§8.4.4 Bound and unbound types
§8.4.5 Satisfying constraints
§8.5 Type parameters
§8.6 Expression tree types
§8.7 The dynamic type
§8.8 Unmanaged types
§9 Variables
§9.1 General
§9.2 Variable categories
§9.2.1 General
§9.2.2 Static variables
§9.2.3 Instance variables
§9.2.3.1 General
§9.2.3.2 Instance variables in classes
§9.2.3.3 Instance variables in structs
§9.2.4 Array elements
§9.2.5 Value parameters
§9.2.6 Reference parameters
§9.2.7 Output parameters
§9.2.8 Local variables
§9.3 Default values
§9.4 Definite assignment
§9.4.1 General
§9.4.2 Initially assigned variables
§9.4.3 Initially unassigned variables
§9.4.4 Precise rules for determining definite assignment
§9.4.4.1 General
§9.4.4.2 General rules for statements
§9.4.4.3 Block statements, checked, and unchecked statements
§9.4.4.4 Expression statements
§9.4.4.5 Declaration statements
§9.4.4.6 If statements
§9.4.4.7 Switch statements
§9.4.4.8 While statements
§9.4.4.9 Do statements
§9.4.4.10 For statements
§9.4.4.11 Break, continue, and goto statements
§9.4.4.12 Throw statements
§9.4.4.13 Return statements
§9.4.4.14 Try-catch statements
§9.4.4.15 Try-finally statements
§9.4.4.16 Try-catch-finally statements
§9.4.4.17 Foreach statements
§9.4.4.18 Using statements
§9.4.4.19 Lock statements
§9.4.4.20 Yield statements
§9.4.4.21 General rules for constant expressions
§9.4.4.22 General rules for simple expressions
§9.4.4.23 General rules for expressions with embedded expressions
§9.4.4.24 Invocation expressions and object creation expressions
§9.4.4.25 Simple assignment expressions
§9.4.4.26 && expressions
§9.4.4.27 || expressions
§9.4.4.28 ! expressions
§9.4.4.29 ?? expressions
§9.4.4.30 ?: expressions
§9.4.4.31 Anonymous functions
§9.4.4.32 Throw expressions
§9.4.4.33 Rules for variables in local functions
§9.5 Variable references
§9.6 Atomicity of variable references
§10 Conversions
§10.1 General
§10.2 Implicit conversions
§10.2.1 General
§10.2.2 Identity conversion
§10.2.3 Implicit numeric conversions
§10.2.4 Implicit enumeration conversions
§10.2.5 Implicit interpolated string conversions
§10.2.6 Implicit nullable conversions
§10.2.7 Null literal conversions
§10.2.8 Implicit reference conversions
§10.2.9 Boxing conversions
§10.2.10 Implicit dynamic conversions
§10.2.11 Implicit constant expression conversions
§10.2.12 Implicit conversions involving type parameters
§10.2.13 User-defined implicit conversions
§10.2.14 Anonymous function conversions and method group conversions
§10.2.15 Default literal conversions
§10.2.16 Implicit throw conversions
§10.3 Explicit conversions
§10.3.1 General
§10.3.2 Explicit numeric conversions
§10.3.3 Explicit enumeration conversions
§10.3.4 Explicit nullable conversions
§10.3.5 Explicit reference conversions
§10.3.6 Unboxing conversions
§10.3.7 Explicit dynamic conversions
§10.3.8 Explicit conversions involving type parameters
§10.3.9 User-defined explicit conversions
§10.4 Standard conversions
§10.4.1 General
§10.4.2 Standard implicit conversions
§10.4.3 Standard explicit conversions
§10.5 User-defined conversions
§10.5.1 General
§10.5.2 Permitted user-defined conversions
§10.5.3 Evaluation of user-defined conversions
§10.5.4 User-defined implicit conversions
§10.5.5 User-defined explicit conversions
§10.6 Conversions involving nullable types
§10.6.1 Nullable Conversions
§10.6.2 Lifted conversions
§10.7 Anonymous function conversions
§10.7.1 General
§10.7.2 Evaluation of anonymous function conversions to delegate types
§10.7.3 Evaluation of lambda expression conversions to expression tree types
§10.8 Method group conversions
§11 Expressions
§11.1 General
§11.2 Expression classifications
§11.2.1 General
§11.2.2 Values of expressions
§11.3 Static and Dynamic Binding
§11.3.1 General
§11.3.2 Binding-time
§11.3.3 Dynamic binding
§11.3.4 Types of subexpressions
§11.4 Operators
§11.4.1 General
§11.4.2 Operator precedence and associativity
§11.4.3 Operator overloading
§11.4.4 Unary operator overload resolution
§11.4.5 Binary operator overload resolution
§11.4.6 Candidate user-defined operators
§11.4.7 Numeric promotions
§11.4.7.1 General
§11.4.7.2 Unary numeric promotions
§11.4.7.3 Binary numeric promotions
§11.4.8 Lifted operators
§11.5 Member lookup
§11.5.1 General
§11.5.2 Base types
§11.6 Function members
§11.6.1 General
§11.6.2 Argument lists
§11.6.2.1 General
§11.6.2.2 Corresponding parameters
§11.6.2.3 Run-time evaluation of argument lists
§11.6.3 Type inference
§11.6.3.1 General
§11.6.3.2 The first phase
§11.6.3.3 The second phase
§11.6.3.4 Input types
§11.6.3.5 Output types
§11.6.3.6 Dependence
§11.6.3.7 Output type inferences
§11.6.3.8 Explicit parameter type inferences
§11.6.3.9 Exact inferences
§11.6.3.10 Lower-bound inferences
§11.6.3.11 Upper-bound inferences
§11.6.3.12 Fixing
§11.6.3.13 Inferred return type
§11.6.3.14 Type inference for conversion of method groups
§11.6.3.15 Finding the best common type of a set of expressions
§11.6.4 Overload resolution
§11.6.4.1 General
§11.6.4.2 Applicable function member
§11.6.4.3 Better function member
§11.6.4.4 Better conversion from expression
§11.6.4.5 Exactly matching expression
§11.6.4.6 Better conversion target
§11.6.4.7 Overloading in generic classes
§11.6.5 Compile-time checking of dynamic member invocation
§11.6.6 Function member invocation
§11.6.6.1 General
§11.6.6.2 Invocations on boxed instances
§11.7 Primary expressions
§11.7.1 General
§11.7.2 Literals
§11.7.3 Interpolated string expressions
§11.7.4 Simple names
§11.7.5 Parenthesized expressions
§11.7.6 Member access
§11.7.6.1 General
§11.7.6.2 Identical simple names and type names
§11.7.7 Null Conditional Member Access
§11.7.8 Invocation expressions
§11.7.8.1 General
§11.7.8.2 Method invocations
§11.7.8.3 Extension method invocations
§11.7.8.4 Delegate invocations
§11.7.9 Null Conditional Invocation Expression
§11.7.10 Element access
§11.7.10.1 General
§11.7.10.2 Array access
§11.7.10.3 Indexer access
§11.7.11 Null Conditional Element Access
§11.7.12 This access
§11.7.13 Base access
§11.7.14 Postfix increment and decrement operators
§11.7.15 The new operator
§11.7.15.1 General
§11.7.15.2 Object creation expressions
§11.7.15.3 Object initializers
§11.7.15.4 Collection initializers
§11.7.15.5 Array creation expressions
§11.7.15.6 Delegate creation expressions
§11.7.15.7 Anonymous object creation expressions
§11.7.16 The typeof operator
§11.7.17 The sizeof operator
§11.7.18 The checked and unchecked operators
§11.7.19 Default value expressions
§11.7.20 Nameof expressions
§11.7.21 Anonymous method expressions
§11.8 Unary operators
§11.8.1 General
§11.8.2 Unary plus operator
§11.8.3 Unary minus operator
§11.8.4 Logical negation operator
§11.8.5 Bitwise complement operator
§11.8.6 Prefix increment and decrement operators
§11.8.7 Cast expressions
§11.8.8 Await expressions
§11.8.8.1 General
§11.8.8.2 Awaitable expressions
§11.8.8.3 Classification of await expressions
§11.8.8.4 Run-time evaluation of await expressions
§11.9 Arithmetic operators
§11.9.1 General
§11.9.2 Multiplication operator
§11.9.3 Division operator
§11.9.4 Remainder operator
§11.9.5 Addition operator
§11.9.6 Subtraction operator
§11.10 Shift operators
§11.11 Relational and type-testing operators
§11.11.1 General
§11.11.2 Integer comparison operators
§11.11.3 Floating-point comparison operators
§11.11.4 Decimal comparison operators
§11.11.5 Boolean equality operators
§11.11.6 Enumeration comparison operators
§11.11.7 Reference type equality operators
§11.11.8 String equality operators
§11.11.9 Delegate equality operators
§11.11.10 Equality operators between nullable value types and the null literal
§11.11.11 The is operator
§11.11.12 The as operator
§11.12 Logical operators
§11.12.1 General
§11.12.2 Integer logical operators
§11.12.3 Enumeration logical operators
§11.12.4 Boolean logical operators
§11.12.5 Nullable Boolean & and | operators
§11.13 Conditional logical operators
§11.13.1 General
§11.13.2 Boolean conditional logical operators
§11.13.3 User-defined conditional logical operators
§11.14 The null coalescing operator
§11.15 The throw expression operator
§11.16 Conditional operator
§11.17 Anonymous function expressions
§11.17.1 General
§11.17.2 Anonymous function signatures
§11.17.3 Anonymous function bodies
§11.17.4 Overload resolution
§11.17.5 Anonymous functions and dynamic binding
§11.17.6 Outer variables
§11.17.6.1 General
§11.17.6.2 Captured outer variables
§11.17.6.3 Instantiation of local variables
§11.17.7 Evaluation of anonymous function expressions
§11.17.8 Implementation Example
§11.18 Query expressions
§11.18.1 General
§11.18.2 Ambiguities in query expressions
§11.18.3 Query expression translation
§11.18.3.1 General
§11.18.3.2 select and group … by clauses with continuations
§11.18.3.3 Explicit range variable types
§11.18.3.4 Degenerate query expressions
§11.18.3.5 From, let, where, join and orderby clauses
§11.18.3.6 Select clauses
§11.18.3.7 Group clauses
§11.18.3.8 Transparent identifiers
§11.18.4 The query-expression pattern
§11.19 Assignment operators
§11.19.1 General
§11.19.2 Simple assignment
§11.19.3 Compound assignment
§11.19.4 Event assignment
§11.20 Expression
§11.21 Constant expressions
§11.22 Boolean expressions
§12 Statements
§12.1 General
§12.2 End points and reachability
§12.3 Blocks
§12.3.1 General
§12.3.2 Statement lists
§12.4 The empty statement
§12.5 Labeled statements
§12.6 Declaration statements
§12.6.1 General
§12.6.2 Local variable declarations
§12.6.3 Local constant declarations
§12.6.4 Local function declarations
§12.7 Expression statements
§12.8 Selection statements
§12.8.1 General
§12.8.2 The if statement
§12.8.3 The switch statement
§12.9 Iteration statements
§12.9.1 General
§12.9.2 The while statement
§12.9.3 The do statement
§12.9.4 The for statement
§12.9.5 The foreach statement
§12.10 Jump statements
§12.10.1 General
§12.10.2 The break statement
§12.10.3 The continue statement
§12.10.4 The goto statement
§12.10.5 The return statement
§12.10.6 The throw statement
§12.11 The try statement
§12.12 The checked and unchecked statements
§12.13 The lock statement
§12.14 The using statement
§12.15 The yield statement
§13 Namespaces
§13.1 General
§13.2 Compilation units
§13.3 Namespace declarations
§13.4 Extern alias directives
§13.5 Using directives
§13.5.1 General
§13.5.2 Using alias directives
§13.5.3 Using namespace directives
§13.5.4 Using static directives
§13.6 Namespace member declarations
§13.7 Type declarations
§13.8 Qualified alias member
§13.8.1 General
§13.8.2 Uniqueness of aliases
§14 Classes
§14.1 General
§14.2 Class declarations
§14.2.1 General
§14.2.2 Class modifiers
§14.2.2.1 General
§14.2.2.2 Abstract classes
§14.2.2.3 Sealed classes
§14.2.2.4 Static classes
§14.2.2.4.1 General
§14.2.2.4.2 Referencing static class types
§14.2.3 Type parameters
§14.2.4 Class base specification
§14.2.4.1 General
§14.2.4.2 Base classes
§14.2.4.3 Interface implementations
§14.2.5 Type parameter constraints
§14.2.6 Class body
§14.2.7 Partial declarations
§14.3 Class members
§14.3.1 General
§14.3.2 The instance type
§14.3.3 Members of constructed types
§14.3.4 Inheritance
§14.3.5 The new modifier
§14.3.6 Access modifiers
§14.3.7 Constituent types
§14.3.8 Static and instance members
§14.3.9 Nested types
§14.3.9.1 General
§14.3.9.2 Fully qualified name
§14.3.9.3 Declared accessibility
§14.3.9.4 Hiding
§14.3.9.5 this access
§14.3.9.6 Access to private and protected members of the containing type
§14.3.9.7 Nested types in generic classes
§14.3.10 Reserved member names
§14.3.10.1 General
§14.3.10.2 Member names reserved for properties
§14.3.10.3 Member names reserved for events
§14.3.10.4 Member names reserved for indexers
§14.3.10.5 Member names reserved for finalizers
§14.4 Constants
§14.5 Fields
§14.5.1 General
§14.5.2 Static and instance fields
§14.5.3 Readonly fields
§14.5.3.1 General
§14.5.3.2 Using static readonly fields for constants
§14.5.3.3 Versioning of constants and static readonly fields
§14.5.4 Volatile fields
§14.5.5 Field initialization
§14.5.6 Variable initializers
§14.5.6.1 General
§14.5.6.2 Static field initialization
§14.5.6.3 Instance field initialization
§14.6 Methods
§14.6.1 General
§14.6.2 Method parameters
§14.6.2.1 General
§14.6.2.2 Value parameters
§14.6.2.3 Reference parameters
§14.6.2.4 Output parameters
§14.6.2.5 Parameter arrays
§14.6.3 Static and instance methods
§14.6.4 Virtual methods
§14.6.5 Override methods
§14.6.6 Sealed methods
§14.6.7 Abstract methods
§14.6.8 External methods
§14.6.9 Partial methods
§14.6.10 Extension methods
§14.6.11 Method body
§14.7 Properties
§14.7.1 General
§14.7.2 Static and instance properties
§14.7.3 Accessors
§14.7.4 Automatically implemented properties
§14.7.5 Accessibility
§14.7.6 Virtual, sealed, override, and abstract accessors
§14.8 Events
§14.8.1 General
§14.8.2 Field-like events
§14.8.3 Event accessors
§14.8.4 Static and instance events
§14.8.5 Virtual, sealed, override, and abstract accessors
§14.9 Indexers
§14.10 Operators
§14.10.1 General
§14.10.2 Unary operators
§14.10.3 Binary operators
§14.10.4 Conversion operators
§14.11 Instance constructors
§14.11.1 General
§14.11.2 Constructor initializers
§14.11.3 Instance variable initializers
§14.11.4 Constructor execution
§14.11.5 Default constructors
§14.12 Static constructors
§14.13 Finalizers
§14.14 Iterators
§14.14.1 General
§14.14.2 Enumerator interfaces
§14.14.3 Enumerable interfaces
§14.14.4 Yield type
§14.14.5 Enumerator objects
§14.14.5.1 General
§14.14.5.2 The MoveNext method
§14.14.5.3 The Current property
§14.14.5.4 The Dispose method
§14.14.6 Enumerable objects
§14.14.6.1 General
§14.14.6.2 The GetEnumerator method
§14.15 Async Functions
§14.15.1 General
§14.15.2 Evaluation of a task-returning async function
§14.15.3 Evaluation of a void-returning async function
§15 Structs
§15.1 General
§15.2 Struct declarations
§15.2.1 General
§15.2.2 Struct modifiers
§15.2.3 Partial modifier
§15.2.4 Struct interfaces
§15.2.5 Struct body
§15.3 Struct members
§15.4 Class and struct differences
§15.4.1 General
§15.4.2 Value semantics
§15.4.3 Inheritance
§15.4.4 Assignment
§15.4.5 Default values
§15.4.6 Boxing and unboxing
§15.4.7 Meaning of this
§15.4.8 Field initializers
§15.4.9 Constructors
§15.4.10 Static constructors
§15.4.11 Automatically implemented properties
§16 Arrays
§16.1 General
§16.2 Array types
§16.2.1 General
§16.2.2 The System.Array type
§16.2.3 Arrays and the generic collection interfaces
§16.3 Array creation
§16.4 Array element access
§16.5 Array members
§16.6 Array covariance
§16.7 Array initializers
§17 Interfaces
§17.1 General
§17.2 Interface declarations
§17.2.1 General
§17.2.2 Interface modifiers
§17.2.3 Variant type parameter lists
§17.2.3.1 General
§17.2.3.2 Variance safety
§17.2.3.3 Variance conversion
§17.2.4 Base interfaces
§17.3 Interface body
§17.4 Interface members
§17.4.1 General
§17.4.2 Interface methods
§17.4.3 Interface properties
§17.4.4 Interface events
§17.4.5 Interface indexers
§17.4.6 Interface member access
§17.5 Qualified interface member names
§17.6 Interface implementations
§17.6.1 General
§17.6.2 Explicit interface member implementations
§17.6.3 Uniqueness of implemented interfaces
§17.6.4 Implementation of generic methods
§17.6.5 Interface mapping
§17.6.6 Interface implementation inheritance
§17.6.7 Interface re-implementation
§17.6.8 Abstract classes and interfaces
§18 Enums
§18.1 General
§18.2 Enum declarations
§18.3 Enum modifiers
§18.4 Enum members
§18.5 The System.Enum type
§18.6 Enum values and operations
§19 Delegates
§19.1 General
§19.2 Delegate declarations
§19.3 Delegate members
§19.4 Delegate compatibility
§19.5 Delegate instantiation
§19.6 Delegate invocation
§20 Exceptions
§20.1 General
§20.2 Causes of exceptions
§20.3 The System.Exception class
§20.4 How exceptions are handled
§20.5 Common exception classes
§21 Attributes
§21.1 General
§21.2 Attribute classes
§21.2.1 General
§21.2.2 Attribute usage
§21.2.3 Positional and named parameters
§21.2.4 Attribute parameter types
§21.3 Attribute specification
§21.4 Attribute instances
§21.4.1 General
§21.4.2 Compilation of an attribute
§21.4.3 Run-time retrieval of an attribute instance
§21.5 Reserved attributes
§21.5.1 General
§21.5.2 The AttributeUsage attribute
§21.5.3 The Conditional attribute
§21.5.3.1 General
§21.5.3.2 Conditional methods
§21.5.3.3 Conditional attribute classes
§21.5.4 The Obsolete attribute
§21.5.5 Caller-info attributes
§21.5.5.1 General
§21.5.5.2 The CallerLineNumber attribute
§21.5.5.3 The CallerFilePath attribute
§21.5.5.4 The CallerMemberName attribute
§21.6 Attributes for interoperation
§22 Unsafe code
§22.1 General
§22.2 Unsafe contexts
§22.3 Pointer types
§22.4 Fixed and moveable variables
§22.5 Pointer conversions
§22.5.1 General
§22.5.2 Pointer arrays
§22.6 Pointers in expressions
§22.6.1 General
§22.6.2 Pointer indirection
§22.6.3 Pointer member access
§22.6.4 Pointer element access
§22.6.5 The address-of operator
§22.6.6 Pointer increment and decrement
§22.6.7 Pointer arithmetic
§22.6.8 Pointer comparison
§22.6.9 The sizeof operator
§22.7 The fixed statement
§22.8 Fixed-size buffers
§22.8.1 General
§22.8.2 Fixed-size buffer declarations
§22.8.3 Fixed-size buffers in expressions
§22.8.4 Definite assignment checking
§22.9 Stack allocation
§A Grammar
§A.1 General
§A.2 Lexical grammar
§A.3 Syntactic grammar
§A.4 Grammar extensions for unsafe code
§B Portability issues
§B.1 General
§B.2 Undefined behavior
§B.3 Implementation-defined behavior
§B.4 Unspecified behavior
§B.5 Other Issues
§C Standard library
§C.1 General
§C.2 Standard Library Types defined in ISO/IEC 23271
§C.3 Standard Library Types not defined in ISO/IEC 23271
§C.4 Format Specifications
§C.5 Library Type Abbreviations
§D Documentation comments
§D.1 General
§D.2 Introduction
§D.3 Recommended tags
§D.3.1 General
§D.3.2 <c>
§D.3.3 <code>
§D.3.4 <example>
§D.3.5 <exception>
§D.3.6 <include>
§D.3.7 <list>
§D.3.8 <para>
§D.3.9 <param>
§D.3.10 <paramref>
§D.3.11 <permission>
§D.3.12 <remarks>
§D.3.13 <returns>
§D.3.14 <see>
§D.3.15 <seealso>
§D.3.16 <summary>
§D.3.17 <typeparam>
§D.3.18 <typeparamref>
§D.3.19 <value>
§D.4 Processing the documentation file
§D.4.1 General
§D.4.2 ID string format
§D.4.3 ID string examples
§D.5 An example
§D.5.1 C# source code
§D.5.2 Resulting XML
§E Bibliography
Foreword
Artículo • 06/02/2023 • Tiempo de lectura: 2 minutos
This specification replaces ECMA-334:2022. Changes from the previous edition include
the addition of the following:
C# tiene un sistema de tipo unificado. Todos los tipos de C#, incluidos los tipos
primitivos como int y double , se heredan de un único tipo object raíz. Por lo tanto,
todos los tipos comparten un conjunto de operaciones comunes, y los valores de todos
los tipos se pueden almacenar, transportar y utilizar de manera coherente. Además, C#
admite tipos de valor y tipos de referencia definidos por el usuario, lo que permite la
asignación dinámica de objetos, así como almacenamiento en línea de estructuras
ligeras.
En el resto de este capítulo se describen las características esenciales del lenguaje C#.
Aunque los capítulos posteriores describen las reglas y las excepciones en un modo
orientado a los detalles y, a veces, de forma matemática, en este capítulo se realiza una
mayor claridad y una brevedad a costa de la integridad. La intención es proporcionar al
lector una introducción al lenguaje que facilitará la escritura de programas iniciales y la
lectura de capítulos posteriores.
Hola a todos
El programa "Hola mundo" tradicionalmente se usa para presentar un lenguaje de
programación. En este caso, se usa C#:
C#
using System;
class Hello
Console.WriteLine("Hello, World");
Consola
csc hello.cs
Consola
Hello, World
El programa "Hola mundo" empieza con una directiva using que hace referencia al
espacio de nombres System . Los espacios de nombres proporcionan un método
jerárquico para organizar las bibliotecas y los programas de C#. Los espacios de
nombres contienen tipos y otros espacios de nombres; por ejemplo, el espacio de
nombres System contiene varios tipos, como la clase Console a la que se hace referencia
en el programa, y otros espacios de nombres, como IO y Collections . Una directiva
using que hace referencia a un espacio de nombres determinado permite el uso no
calificado de los tipos que son miembros de ese espacio de nombres. Debido a la
directiva using , puede utilizar el programa Console.WriteLine como abreviatura de
System.Console.WriteLine .
La clase Hello declarada por el programa "Hola mundo" tiene un miembro único, el
método llamado Main . El método Main se declara con el modificador static . Mientras
que los métodos de instancia pueden hacer referencia a una instancia de objeto
envolvente determinada utilizando la palabra clave this , los métodos estáticos
funcionan sin referencia a un objeto determinado. Por convención, un método estático
denominado Main sirve como punto de entrada de un programa.
En el ejemplo
C#
using System;
namespace Acme.Collections
Entry top;
top = top.next;
return result;
class Entry
this.next = next;
this.data = data;
}
Consola
compila el ejemplo como una biblioteca (código sin un punto de entrada Main y genera
un ensamblado denominado acme.dll .
Los ensamblados contienen código ejecutable en forma de instrucciones de lenguaje
intermedio _ (IL) e información simbólica con el formato _ Metadata *. Antes de
ejecutarlo, el código de IL de un ensamblado se convierte automáticamente en código
específico del procesador mediante el compilador de Just in Time (JIT) de .NET Common
Language Runtime.
C#
using System;
using Acme.Collections;
class Test
s.Push(1);
s.Push(10);
s.Push(100);
Console.WriteLine(s.Pop());
Console.WriteLine(s.Pop());
Console.WriteLine(s.Pop());
Consola
Consola
100
10
Tipos y variables
Hay dos tipos de tipos en C#: *tipos de valor _ y _ tipos de referencia *. Las variables de
tipos de valor contienen directamente los datos, mientras que las variables de los tipos
de referencia almacenan referencias a los datos, lo que se conoce como objetos. Con los
tipos de referencia, es posible que dos variables hagan referencia al mismo objeto y
que, por tanto, las operaciones en una variable afecten al objeto al que hace referencia
la otra variable. Con los tipos de valor, cada variable tiene su propia copia de los datos y
no es posible que las operaciones en una variable afecten a la otra (excepto en el caso
de las variables de parámetro ref y out ).
Los tipos de valor de C# se dividen en *** tipos simples*, _tipos de enumeración_, _tipos
de struct_ y tipos que _aceptan valores NULL_, y los tipos de referencia de C# se dividen en
_tipos de clase_, tipos de _interfaz_, _tipos de matriz*_ y tipos de delegado _ * * *.
Categoría Descripción
Tipos de Tipos simples Entero con signo: sbyte , short , int , long
valor
Booleano: bool
Categoría Descripción
Tipos que aceptan Extensiones de todos los demás tipos de valor con un
valores NULL valor null
Tipos de Tipos de clase Clase base definitiva de todos los demás tipos: object
referencia
Los ocho tipos enteros proporcionan compatibilidad con valores de 8, 16, 32 y 64 bits
en formato con o sin signo.
Los dos tipos de punto flotante, float y double , se representan mediante los formatos
IEEE 754 de precisión sencilla de 32 bits y de doble precisión de 64 bits.
El tipo decimal es un tipo de datos de 128 bits adecuado para cálculos financieros y
monetarios.
El tipo de C# bool se usa para representar valores booleanos: valores que son true o
false .
Los programas de C# utilizan declaraciones de tipos para crear nuevos tipos. Una
declaración de tipos especifica el nombre y los miembros del nuevo tipo. Cinco de las
categorías de tipos de C# las define el usuario: tipos de clase, tipos de estructura, tipos
de interfaz, tipos de enumeración y tipos delegados.
Un tipo de clase define una estructura de datos que contiene miembros de datos
(campos) y miembros de función (métodos, propiedades, etc.). Los tipos de clase
admiten herencia única y polimorfismo, mecanismos por los que las clases derivadas
pueden extender y especializar clases base.
Un tipo de estructura es similar a un tipo de clase en que representa una estructura con
miembros de datos y miembros de función. Sin embargo, a diferencia de las clases, las
estructuras son tipos de valor y no requieren la asignación del montón. Los tipos struct
no admiten la herencia especificada por el usuario y todos los tipos de struct se heredan
implícitamente del tipo object .
Los tipos de clase, estructura, interfaz y delegado admiten genéricos, mediante los
cuales se pueden parametrizar con otros tipos.
Un tipo de enumeración es un tipo distinto con constantes con nombre. Cada tipo de
enumeración tiene un tipo subyacente, que debe ser uno de los ocho tipos enteros. El
conjunto de valores de un tipo de enumeración es igual que el conjunto de valores del
tipo subyacente.
Los tipos que aceptan valores NULL tampoco tienen que declararse antes de que se
puedan utilizar. Para cada tipo de valor que no acepta valores NULL T , existe un tipo
que acepta valores NULL correspondiente T? , que puede contener un valor adicional
null . Por ejemplo, int? es un tipo que puede contener cualquier entero de 32 bits o el
valor null .
C#
using System;
class Test
int i = 123;
object o = i; // Boxing
Cuando un valor de un tipo de valor se convierte al tipo object , se asigna una instancia
de objeto, también denominada "box", para contener el valor, y el valor se copia en ese
cuadro. Por el contrario, cuando una object referencia se convierte en un tipo de valor,
se realiza una comprobación de que el objeto al que se hace referencia es un cuadro del
tipo de valor correcto y, si la comprobación se realiza correctamente, se copia el valor
en el cuadro.
El sistema de tipos unificado de C# significa que los tipos de valor pueden convertirse
en objetos "a petición". Debido a la unificación, las bibliotecas de uso general que
utilizan el tipo object pueden usarse con tipos de referencia y tipos de valor.
Hay varios tipos de variables en C#, entre otras, campos, elementos de matriz, variables
locales y parámetros. Las variables representan ubicaciones de almacenamiento, y cada
variable tiene un tipo que determina qué valores se pueden almacenar en la variable,
como se muestra en la tabla siguiente.
object Una referencia nula, una referencia a un objeto de cualquier tipo de referencia o una
referencia a un valor de conversión boxing de cualquier tipo de valor
Tipo de Una referencia nula, una referencia a una instancia de ese tipo de clase o una
clase referencia a una instancia de una clase derivada de ese tipo de clase.
Tipo de Una referencia nula, una referencia a una instancia de un tipo de clase que
interfaz implementa ese tipo de interfaz o una referencia a un valor de conversión boxing de
un tipo de valor que implementa ese tipo de interfaz.
Tipo de Una referencia nula, una referencia a una instancia de ese tipo de matriz o una
matriz referencia a una instancia de un tipo de matriz compatible
Tipo de Contenido posible
variable
Tipo Una referencia nula o una referencia a una instancia de ese tipo de delegado.
delegado
Expresiones
Las expresiones _ se construyen a partir de _operandos_ y _operadores*. and *operators*.
Los operadores de una expresión indican qué operaciones se aplican a los operandos.
Ejemplos de operadores incluyen + , - , _ , / y new . Algunos ejemplos de operandos son
literales, campos, variables locales y expresiones.
x++ Postincremento
x-- Postdecremento
Unario +x Identidad
-x Negación
!x Negación lógica
++x Preincremento
--x Predecremento
Multiplicativa x * y Multiplicación
x / y División
x % y Resto
Igualdad x == y Igual
x != y No igual a
Instrucciones
Las acciones de un programa se expresan mediante instrucciones. C# admite varios
tipos de instrucciones diferentes, varias de las cuales se definen en términos de
instrucciones insertadas.
Las instrucciones de salto se usan para transferir el control. En este grupo están las
instrucciones break , continue , goto , throw , return y yield .
La instrucción try ... catch se usa para detectar excepciones que se producen durante la
ejecución de un bloque, y la instrucción try ... finally se usa para especificar el código
de finalización que siempre se ejecuta, tanto si se ha producido una excepción como si
no.
La instrucción lock se usa para obtener el bloqueo de exclusión mutua para un objeto
determinado, ejecutar una instrucción y, luego, liberar el bloqueo.
La instrucción using se usa para obtener un recurso, ejecutar una instrucción y, luego,
eliminar dicho recurso.
C#
int a;
int b = 2, c = 3;
a = 1;
Console.WriteLine(a + b + c);
Console.WriteLine(pi * r * r);
Expression (Instrucción)
C#
int i;
Instrucción if
C#
if (args.Length == 0) {
Console.WriteLine("No arguments");
else {
Instrucción switch
C#
int n = args.Length;
switch (n) {
case 0:
Console.WriteLine("No arguments");
break;
case 1:
Console.WriteLine("One argument");
break;
default:
break;
Instrucción while
C#
int i = 0;
Console.WriteLine(args[i]);
i++;
Instrucción do
C#
string s;
do {
s = Console.ReadLine();
if (s != null) Console.WriteLine(s);
} while (s != null);
Instrucción for
C#
Console.WriteLine(args[i]);
Instrucción foreach
C#
Console.WriteLine(s);
Instrucción break
C#
while (true) {
string s = Console.ReadLine();
if (s == null) break;
Console.WriteLine(s);
Instrucción continue
C#
if (args[i].StartsWith("/")) continue;
Console.WriteLine(args[i]);
Instrucción goto
C#
int i = 0;
goto check;
loop:
Console.WriteLine(args[i++]);
check:
Instrucción return
C#
return a + b;
Console.WriteLine(Add(1, 2));
return;
Instrucción yield
C#
yield return i;
}
yield break;
Console.WriteLine(x);
throw``try instrucciones y
C#
return x / y;
try {
if (args.Length != 2) {
double x = double.Parse(args[0]);
double y = double.Parse(args[1]);
Console.WriteLine(Divide(x, y));
catch (Exception e) {
Console.WriteLine(e.Message);
finally {
Console.WriteLine("Good bye!");
checked``unchecked instrucciones y
C#
int i = int.MaxValue;
checked {
unchecked {
Instrucción lock
C#
class Account
decimal balance;
lock (this) {
balance -= amount;
Instrucción using
C#
w.WriteLine("Line one");
w.WriteLine("Line two");
w.WriteLine("Line three");
Clases y objetos
*Las clases _ son los tipos más fundamentales de C#. Una clase es una estructura de
datos que combina estados (campos) y acciones (métodos y otros miembros de
función) en una sola unidad. Una clase proporciona una definición para las instancias
creadas dinámicamente de la clase, también conocidas como objetos. Las clases admiten
la herencia y el polimorfismo, mecanismos por los que las clases derivadas pueden
extender y especializar _ clases base *.
Las clases nuevas se crean mediante declaraciones de clase. Una declaración de clase se
inicia con un encabezado que especifica los atributos y modificadores de la clase, el
nombre de la clase, la clase base (si se indica) y las interfaces implementadas por la
clase. Al encabezado le sigue el cuerpo de la clase, que consta de una lista de
declaraciones de miembros escritas entre los delimitadores { y } .
C#
public int x, y;
this.x = x;
this.y = y;
Las instancias de clases se crean mediante el operador new , que asigna memoria para
una nueva instancia, invoca un constructor para inicializar la instancia y devuelve una
referencia a la instancia. Las instrucciones siguientes crean dos objetos Point y
almacenan las referencias en esos objetos en dos variables:
C#
Miembros
Los miembros de una clase son *miembros estáticos _ o _ miembros de instancia *. Los
miembros estáticos pertenecen a clases y los miembros de instancia pertenecen a
objetos (instancias de clases).
Member Descripción
Destructores Acciones que deben realizarse antes de que las instancias de la clase se descarten
de forma permanente
Accesibilidad
Cada miembro de una clase tiene asociada una accesibilidad, que controla las regiones
del texto del programa que pueden tener acceso al miembro. Existen cinco formas
posibles de accesibilidad. Estos se resumen en la siguiente tabla.
Accesibilidad Significado
protected Acceso limitado a esta clase o a las clases derivadas de esta clase
protected internal Acceso limitado a este programa o a las clases derivadas de esta clase
Parámetros de tipo
Una definición de clase puede especificar un conjunto de parámetros de tipo poniendo
tras el nombre de clase una lista de nombres de parámetro de tipo entre corchetes
angulares. Los parámetros de tipo pueden usarse luego en el cuerpo de las
declaraciones de clase para definir a los miembros de la clase. En el ejemplo siguiente,
los parámetros de tipo de Pair son TFirst y TSecond :
C#
public class Pair<TFirst,TSecond>
Un tipo de clase que se declara para tomar parámetros de tipo se denomina tipo de
clase genérico. Los tipos struct, interfaz y delegado también pueden ser genéricos.
Cuando se usa la clase genérica, se deben proporcionar argumentos de tipo para cada
uno de los parámetros de tipo:
C#
Clases base
Una declaración de clase puede especificar una clase base colocando después del
nombre de clase y los parámetros de tipo dos puntos seguidos del nombre de la clase
base. Omitir una especificación de la clase base es igual que derivarla del tipo object .
En el ejemplo siguiente, la clase base de Point3D es Point y la clase base de Point es
object :
C#
public int x, y;
this.x = x;
this.y = y;
public int z;
this.z = z;
Una clase hereda a los miembros de su clase base. La herencia significa que una clase
contiene implícitamente todos los miembros de su clase base, excepto la instancia y
constructores estáticos, y los destructores de la clase base. Una clase derivada puede
agregar nuevos miembros a aquellos de los que hereda, pero no puede quitar la
definición de un miembro heredado. En el ejemplo anterior, Point3D hereda los campos
x y y de Point y cada instancia de Point3D contiene tres campos: x , y y z .
Existe una conversión implícita de un tipo de clase a cualquiera de sus tipos de clase
base. Por lo tanto, una variable de un tipo de clase puede hacer referencia a una
instancia de esa clase o a una instancia de cualquier clase derivada. Por ejemplo, dadas
las declaraciones de clase anteriores, una variable de tipo Point puede hacer referencia
a una instancia de Point o Point3D :
C#
Campos
Un campo es una variable que está asociada con una clase o a una instancia de una
clase.
En el ejemplo siguiente, cada instancia de la clase Color tiene una copia independiente
de los campos de instancia r , g y b , pero solo hay una copia de los campos estáticos
Black , White , Red , Green y Blue :
C#
public class Color
private byte r, g, b;
this.r = r;
this.g = g;
this.b = b;
Como se muestra en el ejemplo anterior, los campos de solo lectura se puede declarar
con un modificador readonly . La asignación a un readonly campo solo se puede
producir como parte de la declaración del campo o en un constructor de la misma clase.
Métodos
Un *método _ es un miembro que implementa un cálculo o una acción que puede
realizar un objeto o una clase. Se tiene acceso a los métodos estáticos a través de la
clase. _ Se tiene acceso a los métodos de instancia* a través de instancias de la clase.
Los métodos tienen una lista (posiblemente vacía) de *Parameters _, que representan
valores o referencias a variables que se pasan al método, y un tipo de valor devuelto _ *
* *, que especifica el tipo del valor calculado y devuelto por el método. El tipo de valor
devuelto de un método es void si no devuelve un valor.
Al igual que los tipos, los métodos también pueden tener un conjunto de parámetros de
tipo, para lo cuales se deben especificar argumentos de tipo cuando se llama al método.
A diferencia de los tipos, los argumentos de tipo a menudo se pueden deducir de los
argumentos de una llamada al método y no es necesario proporcionarlos
explícitamente.
Parámetros
Los parámetros se usan para pasar valores o referencias a variables a métodos. Los
parámetros de un método obtienen sus valores reales de los argumentos que se
especifican cuando se invoca el método. Hay cuatro tipos de parámetros: parámetros de
valor, parámetros de referencia, parámetros de salida y matrices de parámetros.
C#
using System;
class Test
int temp = x;
x = y;
y = temp;
int i = 1, j = 2;
C#
using System;
class Test
static void Divide(int x, int y, out int result, out int remainder) {
result = x / y;
remainder = x % y;
C#
...
C#
C#
string s = "x={0} y={1} z={2}";
args[0] = x;
args[1] = y;
args[2] = z;
Console.WriteLine(s, args);
Un cuerpo del método puede declarar variables que son específicas de la invocación del
método. Estas variables se denominan variables locales. Una declaración de variable
local especifica un nombre de tipo, un nombre de variable y, posiblemente, un valor
inicial. En el ejemplo siguiente se declara una variable local i con un valor inicial de
cero y una variable local j sin ningún valor inicial.
C#
using System;
class Squares
int i = 0;
int j;
j = i * i;
i = i + 1;
C# requiere que se asigne definitivamente una variable local antes de que se pueda
obtener su valor. Por ejemplo, si la declaración de i anterior no incluyera un valor
inicial, el compilador notificaría un error con los usos posteriores de i porque i no se
asignaría definitivamente en esos puntos del programa.
Puede usar una instrucción return para devolver el control a su llamador. En un método
que devuelve void , las instrucciones return no pueden especificar una expresión. En un
método que devuelve void instrucciones no, return debe incluir una expresión que
calcule el valor devuelto.
Métodos estáticos y de instancia
Un método declarado con un modificador static es un método estático. Un método
estático no opera en una instancia específica y solo puede acceder directamente a
miembros estáticos.
C#
class Entity
int serialNo;
public Entity() {
serialNo = nextSerialNo++;
return serialNo;
return nextSerialNo;
nextSerialNo = value;
C#
using System;
class Test
Entity.SetNextSerialNo(1000);
Un método virtual puede ser reemplazado en una clase derivada. Cuando una
declaración de método de instancia incluye un override modificador, el método
invalida un método virtual heredado con la misma firma. Mientras que una declaración
de método virtual introduce un método nuevo, una declaración de método de
reemplazo especializa un método virtual heredado existente proporcionando una nueva
implementación de ese método.
variables y operaciones aritméticas. (Esto es similar a, pero no debe confundirse con los
tipos de árbol de expresión introducidos en los tipos de árbol de expresión).
C#
using System;
using System.Collections;
double value;
this.value = value;
return value;
string name;
this.name = name;
if (value == null) {
return Convert.ToDouble(value);
Expression left;
char op;
Expression right;
this.left = left;
this.op = op;
this.right = right;
double x = left.Evaluate(vars);
double y = right.Evaluate(vars);
switch (op) {
Las cuatro clases anteriores se pueden usar para modelar expresiones aritméticas. Por
ejemplo, usando instancias de estas clases, la expresión x + 3 se puede representar de
la manera siguiente.
C#
new VariableReference("x"),
'+',
new Constant(3));
using System;
using System.Collections;
class Test
new VariableReference("x"),
'*',
new Operation(
new VariableReference("y"),
'+',
new Constant(2)
);
vars["x"] = 3;
vars["y"] = 5;
vars["x"] = 1.5;
vars["y"] = 9;
Sobrecarga de métodos
C#
class Test
Console.WriteLine("F()");
}
Console.WriteLine("F(object)");
Console.WriteLine("F(int)");
Console.WriteLine("F(double)");
Console.WriteLine("F<T>(T)");
Console.WriteLine("F(double, double)");
C#
// Constant...
// Fields...
T[] items;
int count;
// Constructors...
// Properties...
get {
return items.Length;
set {
if (value != items.Length) {
items = newItems;
}
// Indexer...
get {
return items[index];
set {
items[index] = value;
OnChanged();
// Methods...
items[count] = item;
count++;
OnChanged();
if (!object.Equals(a.items[i], b.items[i])) {
return false;
return true;
// Event...
// Operators...
Constructores
C# admite constructores de instancia y estáticos. Un *constructor de instancia _ es un
miembro que implementa las acciones necesarias para inicializar una instancia de una
clase. Un constructor _ static* es un miembro que implementa las acciones necesarias
para inicializar una clase en sí misma cuando se carga por primera vez.
C#
Propiedades
*Properties _ son una extensión natural de los campos. Ambos son miembros con
nombre con tipos asociados y la sintaxis para acceder a los campos y las propiedades es
la misma. Sin embargo, a diferencia de los campos, las propiedades no denotan
ubicaciones de almacenamiento. En su lugar, las propiedades tienen _ descriptores de
acceso* que especifican las instrucciones que se ejecutarán cuando se lean o escriban
sus valores.
Una propiedad se declara como un campo, salvo que la declaración finaliza con un get
descriptor de acceso o un set descriptor de acceso escrito entre los delimitadores { y
en } lugar de terminar en un punto y coma. Una propiedad que tiene tanto un
descriptor de acceso get como un set descriptor de acceso es una propiedad * de
lectura y escritura , una propiedad que solo tiene un get descriptor de acceso es una
propiedad de solo lectura y una propiedad que solo tiene un set descriptor de acceso es
una propiedad de solo escritura* *.
La clase List<T> declara dos propiedades, Count y Capacity , que son de solo lectura y
de lectura y escritura, respectivamente. El siguiente es un ejemplo de uso de estas
propiedades.
C#
Los descriptores de acceso de una propiedad pueden ser virtuales. Cuando una
declaración de propiedad incluye un modificador virtual , abstract o override , se
aplica a los descriptores de acceso de la propiedad.
Indexadores
Un indexador es un miembro que permite indexar de la misma manera que una matriz.
Un indexador se declara como una propiedad, excepto por el hecho que el nombre del
miembro es this , seguido por una lista de parámetros que se escriben entre los
delimitadores [ y ] . Los parámetros están disponibles en los descriptores de acceso del
indexador. De forma similar a las propiedades, los indexadores pueden ser lectura y
escritura, de solo lectura y de solo escritura, y los descriptores de acceso de un
indexador pueden ser virtuales.
La clase List declara un único indexador de lectura y escritura que toma un parámetro
int . El indexador permite indexar instancias de List con valores int . Por ejemplo
C#
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
string s = names[i];
names[i] = s.ToUpper();
Los indexadores se pueden sobrecargar, lo que significa que una clase puede declarar
varios indexadores siempre y cuando el número o los tipos de sus parámetros sean
diferentes.
Eventos
Un evento es un miembro que permite que una clase u objeto proporcionen
notificaciones. Un evento se declara como un campo, excepto por el hecho de que la
declaración incluye una palabra clave event , y el tipo debe ser un tipo delegado.
Dentro de una clase que declara un miembro de evento, el evento se comporta como
un campo de un tipo delegado (siempre que el evento no sea abstracto y no declare
descriptores de acceso). El campo almacena una referencia a un delegado que
representa los controladores de eventos que se han agregado al evento. Si no hay
ningún controlador de eventos presente, el campo es null .
La clase List<T> declara un único miembro de evento llamado Changed , lo que indica
que se ha agregado un nuevo elemento a la lista. El Changed evento lo desencadena el
OnChanged método virtual, que primero comprueba si el evento es null (lo que significa
C#
using System;
class Test
changeCount++;
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
List<int> .
C#
using System;
class Test
a.Add(1);
a.Add(2);
b.Add(1);
b.Add(2);
b.Add(3);
El primer objeto Console.WriteLine genera True porque las dos listas contienen el
mismo número de objetos con los mismos valores en el mismo orden. Si List<T> no
hubiera definido operator== , el primer objeto Console.WriteLine habría generado
False porque a y b hacen referencia a diferentes instancias de List<int> .
Destructores
Un destructor es un miembro que implementa las acciones necesarias para destruir una
instancia de una clase. Los destructores no pueden tener parámetros, no pueden tener
modificadores de accesibilidad y no se pueden invocar explícitamente. El destructor de
una instancia se invoca automáticamente durante la recolección de elementos no
utilizados.
El recolector de elementos no utilizados tiene una latitud ancha a la hora de decidir
cuándo recopilar objetos y ejecutar destructores. En concreto, el tiempo de las
invocaciones de destructor no es determinista y los destructores se pueden ejecutar en
cualquier subproceso. Por estas y otras razones, las clases deben implementar
destructores solo cuando no sean factibles otras soluciones.
Estructuras
Al igual que las clases, los structs son estructuras de datos que pueden contener
miembros de datos y miembros de función, pero a diferencia de las clases, los structs
son tipos de valor y no requieren asignación del montón. Una variable de un tipo de
struct almacena directamente los datos del struct, mientras que una variable de un tipo
de clase almacena una referencia a un objeto asignado dinámicamente. Los tipos struct
no admiten la herencia especificada por el usuario y todos los tipos de struct se heredan
implícitamente del tipo object .
Los structs son particularmente útiles para estructuras de datos pequeñas que tengan
semánticas de valor. Los números complejos, los puntos de un sistema de coordenadas
o los pares clave-valor de un diccionario son buenos ejemplos de structs. El uso de un
struct en lugar de una clase para estructuras de datos pequeñas puede suponer una
diferencia sustancial en el número de asignaciones de memoria que realiza una
aplicación. Por ejemplo, el siguiente programa crea e inicializa una matriz de 100
puntos. Si Point se implementa como una clase, se crean instancias de 101 objetos
distintos: uno para la matriz y uno por cada uno de los 100 elementos.
C#
class Point
public int x, y;
this.x = x;
this.y = y;
class Test
C#
struct Point
public int x, y;
this.x = x;
this.y = y;
Los structs se invocan con el operador new , pero eso no implica que se asigne memoria.
En lugar de asignar dinámicamente un objeto y devolver una referencia a él, un
constructor de structs simplemente devuelve el valor del struct propiamente dicho
(normalmente en una ubicación temporal en la pila) y este valor se copia luego cuando
es necesario.
Con las clases, es posible que dos variables hagan referencia al mismo objeto y, que por
tanto, las operaciones en una variable afecten al objeto al que hace referencia la otra
variable. Con los struct, cada variable tiene su propia copia de los datos y no es posible
que las operaciones en una afecten a la otra. Por ejemplo, la salida generada por el
fragmento de código siguiente depende de si Point es una clase o un struct.
C#
Point b = a;
a.x = 20;
Console.WriteLine(b.x);
En el ejemplo anterior se resaltan dos de las limitaciones de los structs. En primer lugar,
copiar un struct entero normalmente es menos eficaz que copiar una referencia a un
objeto, por lo que el paso de parámetros de asignación y valor puede ser más costoso
con structs que con tipos de referencia. En segundo lugar, a excepción de los
parámetros ref y out , no es posible crear referencias a structs, que excluyen su uso en
varias situaciones.
Matrices
*Array _ es una estructura de datos que contiene un número de variables a las que se
tiene acceso a través de índices calculados. Las variables contenidas en una matriz,
también denominadas elementos de la matriz, son todas del mismo tipo y este tipo se
denomina el tipo de elemento _ * de la matriz.
Los tipos de matriz son tipos de referencia, y la declaración de una variable de matriz
simplemente establece un espacio reservado para una referencia a una instancia de
matriz. Las instancias de matriz reales se crean dinámicamente en tiempo de ejecución
mediante el new operador. La operación new especifica la longitud de la nueva instancia
de matriz, que luego se fija para la vigencia de la instancia. Los índices de los elementos
de una matriz van de 0 a Length - 1 . El operador new inicializa automáticamente los
elementos de una matriz a su valor predeterminado, que, por ejemplo, es cero para
todos los tipos numéricos y null para todos los tipos de referencias.
C#
using System;
class Test
a[i] = i * i;
El tipo de elemento de una matriz puede ser cualquiera, incluido un tipo de matriz. Una
matriz con elementos de un tipo de matriz a veces se conoce como matriz escalonada
porque las longitudes de las matrices de elementos no tienen que ser iguales. En el
ejemplo siguiente se asigna una matriz de matrices de int :
C#
La primera línea crea una matriz con tres elementos, cada uno de tipo int[] y cada uno
con un valor inicial de null . Las líneas posteriores inicializan entonces los tres
elementos con referencias a instancias de matriz individuales de longitud variable.
El operador new permite especificar los valores iniciales de los elementos de matriz
mediante un inicializador de matriz, que es una lista de las expresiones escritas entre
los delimitadores { y } . En el ejemplo siguiente se asigna e inicializa un tipo int[] con
tres elementos.
C#
C#
t[0] = 1;
t[1] = 2;
t[2] = 3;
int[] a = t;
Interfaces
Una interfaz define un contrato que se puede implementar mediante clases y structs.
Una interfaz puede contener métodos, propiedades, eventos e indexadores. Una interfaz
no proporciona implementaciones de los miembros que define, simplemente especifica
los miembros que se deben proporcionar mediante clases o structs que implementan la
interfaz.
C#
interface IControl
void Paint();
Las clases y los structs pueden implementar varias interfaces. En el ejemplo siguiente, la
clase EditBox implementa IControl y IDataBound .
C#
interface IDataBound
Cuando una clase o un struct implementan una interfaz determinada, las instancias de
esa clase o struct se pueden convertir implícitamente a ese tipo de interfaz. Por ejemplo
C#
C#
C#
Solo se puede acceder a los miembros de interfaz explícitos mediante el tipo de interfaz.
Por ejemplo, la implementación de IControl.Paint proporcionada por la EditBox clase
anterior solo se puede invocar convirtiendo primero la EditBox referencia al IControl
tipo de interfaz.
C#
control.Paint(); // Ok
Enumeraciones
Un tipo de enumeración es un tipo de valor distinto con un conjunto de constantes con
nombre. En el ejemplo siguiente se declara y se utiliza un tipo de enumeración
denominado Color con tres valores constantes,, Red Green y Blue .
C#
using System;
enum Color
Red,
Green,
Blue
class Test
switch (color) {
case Color.Red:
Console.WriteLine("Red");
break;
case Color.Green:
Console.WriteLine("Green");
break;
case Color.Blue:
Console.WriteLine("Blue");
break;
default:
Console.WriteLine("Unknown color");
break;
Color c = Color.Red;
PrintColor(c);
PrintColor(Color.Blue);
C#
Left = -1,
Center = 0,
Right = 1
C#
C#
Color c = 0;
Delegados
Un tipo de delegado representa las referencias a métodos con una lista de parámetros
determinada y un tipo de valor devuelto. Los delegados permiten tratar métodos como
entidades que se puedan asignar a variables y se puedan pasar como parámetros. Los
delegados son similares al concepto de punteros de función en otros lenguajes, pero a
diferencia de los punteros de función, los delegados están orientados a objetos y
presentan seguridad de tipos.
C#
using System;
class Multiplier
double factor;
this.factor = factor;
return x * factor;
class Test
return result;
Una instancia del tipo de delegado Function puede hacer referencia a cualquier método
que tome un argumento double y devuelva un valor double . El método Apply aplica un
elemento Function determinado a los elementos de double[] y devuelve double[] con
los resultados. En el método Main , Apply se usa para aplicar tres funciones diferentes a
un valor double[] .
Los delegados también pueden crearse mediante funciones anónimas, que son
"métodos insertados" que se crean sobre la marcha. Las funciones anónimas pueden ver
las variables locales de los métodos adyacentes. Por lo tanto, el ejemplo de
multiplicador anterior se puede escribir más fácilmente sin usar una Multiplier clase:
C#
Atributos
Los tipos, los miembros y otras entidades en un programa de C # admiten
modificadores que controlan ciertos aspectos de su comportamiento. Por ejemplo, la
accesibilidad de un método se controla mediante los modificadores public , protected ,
internal y private . C # generaliza esta funcionalidad de manera que los tipos de
información declarativa definidos por el usuario se puedan adjuntar a las entidades del
programa y recuperarse en tiempo de ejecución. Los programas especifican esta
información declarativa adicional mediante la definición y el uso de atributos.
C#
using System;
string url;
string topic;
this.url = url;
C#
[Help("http://msdn.microsoft.com/.../MyClass.htm")]
C#
using System;
using System.Reflection;
class Test
HelpAttribute a = Attribute.GetCustomAttribute(member,
typeof(HelpAttribute)) as HelpAttribute;
if (a == null) {
else {
ShowHelp(typeof(Widget));
ShowHelp(typeof(Widget).GetMethod("Display"));
This specification describes the form and establishes the interpretation of programs
written in the C# programming language. It describes
The following normative documents contain provisions, which, through reference in this
text, constitute provisions of this specification. For dated references, subsequent
amendments to, or revisions of, any of these publications do not apply. However, parties
to agreements based on this specification are encouraged to investigate the possibility
of applying the most recent editions of the normative documents indicated below. For
undated references, the latest edition of the normative document referred to applies.
Members of ISO and IEC maintain registers of currently valid specifications.
ISO 80000-2, Quantities and units — Part 2: Mathematical signs and symbols to be used
in the natural sciences and technology.
For the purposes of this specification, the following definitions apply. Other terms are
defined where they appear in italic type or on the left side of a syntax rule. Terms
explicitly defined in this specification are not to be presumed to refer implicitly to similar
terms defined elsewhere. Terms not defined in this specification are to be interpreted
according to ISO/IEC 2382.1. Mathematical symbols not defined in this specification are
to be interpreted according to ISO 80000-2.
application
assembly with an entry point
application domain
entity that enables application isolation by acting as a container for application
state
argument
expression in the comma-separated list bounded by the parentheses in a
method or instance constructor call expression or bounded by the square
brackets in an element access expression
assembly
one or more files output by the compiler as a result of program compilation
behavior
external appearance or action
behavior, implementation-defined
unspecified behavior where each implementation documents how the choice is
made
behavior, undefined
behavior, upon use of a non-portable or erroneous construct or of erroneous
data, for which this specification imposes no requirements
behavior, unspecified
behavior where this specification provides two or more possibilities and
imposes no further requirements on which is chosen in any instance
character (when used without a qualifier)
In the context of a non-Unicode encoding, the meaning of character in that
encoding; or
In the context of a character literal or a value of type char, a Unicode code point
in the range U+0000 to U+FFFF (including surrogate code points), that is a UTF-
16 code unit; or
Otherwise, a Unicode code point
class library
assembly that can be used by other assemblies
compilation unit
ordered sequence of Unicode characters that is input to a compiler
diagnostic message
message belonging to an implementation-defined subset of the
implementation’s output messages
error, compile-time
error reported during program translation
exception
exceptional condition reported during program execution
implementation
particular set of software (running in a particular translation environment under
particular control options) that performs translation of programs for, and
supports execution of methods in, a particular execution environment
module
the contents of an assembly produced by a compiler. Some implementations
may have facilities to produce assemblies that contain more than one module.
The behavior in such situations is outside the scope of this specification
namespace
logical organizational system grouping related program elements
parameter
variable declared as part of a method, instance constructor, operator, or indexer
definition, which acquires a value on entry to that function member
program
one or more compilation units that are presented to the compiler and are run or
executed by an execution environment
unsafe code
code that is permitted to perform such lower-level operations as declaring and
operating on pointers, performing conversions between pointers and integral
types, and taking the address of variables
warning, compile-time
informational message reported during program translation, which is intended
to identify a potentially questionable usage of a program element
4 General description
Artículo • 08/04/2022 • Tiempo de lectura: 2 minutos
This standard is divided into the following subdivisions: front matter; language syntax,
constraints, and semantics; and annexes.
As such, conformance is most important, and the bulk of this specification is aimed at
specifying the characteristics that make C# implementations and C# programs
conforming ones.
The text in this specification that specifies requirements is considered normative. All
other text in this specification is informative; that is, for information purposes only.
Unless stated otherwise, all text is normative. Normative text is further broken into
required and conditional categories. Conditionally normative text specifies a feature
and its requirements where the feature is optional. However, if that feature is provided,
its syntax and semantics shall be exactly as specified.
A strictly conforming program shall use only those features of the language specified in
this specification as being required. (This means that a strictly conforming program
cannot use any conditionally normative feature.) It shall not produce output dependent
on any unspecified, undefined, or implementation-defined behavior.
A conforming implementation of C# shall provide and support all the types, values,
objects, properties, methods, and program syntax and semantics described in the
normative (but not the conditionally normative) parts in this specification.
Programas
Un programa de C# * _ consta de uno o varios archivos de código fuente, conocido
formalmente como unidades de compilación* (unidades de compilación). Un archivo de
origen es una secuencia ordenada de caracteres Unicode. Los archivos de origen suelen
tener una correspondencia uno a uno con los archivos de un sistema de archivos, pero
esta correspondencia no es necesaria. Para obtener la máxima portabilidad, se
recomienda codificar los archivos de un sistema de archivos con la codificación UTF-8.
Gramáticas
Esta especificación presenta la sintaxis del lenguaje de programación C# con dos
gramáticas. La gramática léxica _ (gramática léxica) define cómo se combinan los
caracteres Unicode para formar terminadores de línea, espacios en blanco, comentarios,
tokens y directivas de procesamiento previo. La _ gramática sintáctica* (gramática
sintáctica) define el modo en que los tokens resultantes de la gramática léxica se
combinan para formar programas de C#.
Notación gramatical
Las gramáticas léxicas y sintácticas se presentan en Backus-Naur formulario mediante la
notación de la herramienta de gramática ANTLR.
Gramática léxica
La gramática léxica de C# se presenta en el análisis léxico, los tokensy las directivas de
procesamiento previo. Los símbolos de terminal de la gramática léxica son los caracteres
del juego de caracteres Unicode y la gramática léxica especifica cómo se combinan los
caracteres para formar tokens (tokens), espacios en blanco (espacio en blanco),
comentarios (comentarios) y directivas de preprocesamiento (directivas de
procesamiento previo).
Gramática sintáctica
La gramática sintáctica de C# se presenta en los capítulos y los apéndices que siguen
este capítulo. Los símbolos de terminal de la gramática sintáctica son los tokens
definidos por la gramática léxica y la gramática sintáctica especifica cómo se combinan
los tokens para formar programas de C#.
Análisis léxico
La producción de entrada define la estructura léxica de un archivo de código fuente de
C#. Cada archivo de código fuente de un programa de C# debe ajustarse a esta
producción de gramática léxica.
antlr
input
: input_section?
input_section
: input_section_part+
input_section_part
: input_element* new_line
| pp_directive
input_element
: whitespace
| comment
| token
Terminadores de línea
Los terminadores de línea dividen los caracteres de un archivo de código fuente de C#
en líneas.
antlr
new_line
Por compatibilidad con las herramientas de edición de código fuente que agregan
marcadores de fin de archivo y para permitir que un archivo de código fuente se vea
como una secuencia de líneas terminadas correctamente, se aplican las
transformaciones siguientes, en orden, a cada archivo de código fuente de un programa
de C#:
Comentarios
Se admiten dos formatos de comentarios: de una sola línea y delimitados.\ Comentarios
de una sola línea: empiece con los caracteres // y amplíe hasta el final de la línea de
código fuente. Los _comentarios delimitados_ comienzan con los caracteres /_ y
terminan con los caracteres */ . Los comentarios delimitados pueden abarcar varias
líneas.
antlr
comment
: single_line_comment
| delimited_comment
single_line_comment
: '//' input_character*
input_character
new_line_character
delimited_comment
delimited_comment_section
: '/'
| asterisk* not_slash_or_asterisk
asterisk
: '*'
not_slash_or_asterisk
En el ejemplo
C#
*/
class Hello
System.Console.WriteLine("hello, world");
En el ejemplo
C#
//
System.Console.WriteLine("hello, world");
Espacio en blanco
El espacio en blanco se define como cualquier carácter con la clase Unicode ZS (que
incluye el carácter de espacio), así como el carácter de tabulación horizontal, el carácter
de tabulación vertical y el carácter de avance de la forma.
antlr
whitespace
Tokens
Hay varios tipos de tokens: identificadores, palabras clave, literales, operadores y signos
de puntuación. Los espacios en blanco y los comentarios no son tokens, aunque actúan
como separadores de tokens.
antlr
token
: identifier
| keyword
| integer_literal
| real_literal
| character_literal
| string_literal
| interpolated_string_literal
| operator_or_punctuator
unicode_escape_sequence
Una secuencia de escape Unicode representa el carácter Unicode que forma el número
hexadecimal después de los \u caracteres "" o "" \U . Dado que C# usa una codificación
de 16 bits de puntos de código Unicode en caracteres y valores de cadena, no se
permite un carácter Unicode en el intervalo de U + 10000 a U + 10FFFF en un literal de
carácter y se representa mediante un par suplente Unicode en un literal de cadena. No
se admiten los caracteres Unicode con puntos de código anteriores a 0x10FFFF.
No se realizan varias traducciones. Por ejemplo, el literal de cadena " \u005Cu005C " es
equivalente a " \u005C " en lugar de " \ ". El valor Unicode \u005C es el carácter " \ ".
En el ejemplo
C#
class Class1
char c = '\u0066';
if (\u0066)
System.Console.WriteLine(c.ToString());
muestra varios usos de \u0066 , que es la secuencia de escape para la letra " f ". El
programa es equivalente a
C#
class Class1
char c = 'f';
if (f)
System.Console.WriteLine(c.ToString());
Identificadores
Las reglas para los identificadores que se proporcionan en esta sección corresponden
exactamente a las recomendadas por el Anexo 31 del estándar Unicode, con la
excepción de que el carácter de subrayado se permite como carácter inicial (como el
tradicional en el lenguaje de programación C), las secuencias de escape Unicode se
permiten en los identificadores y el @ carácter "
antlr
identifier
: available_identifier
| '@' identifier_or_keyword
available_identifier
identifier_or_keyword
: identifier_start_character identifier_part_character*
identifier_start_character
: letter_character
| '_'
identifier_part_character
: letter_character
| decimal_digit_character
| connecting_character
| combining_character
| formatting_character
letter_character
: '<A Unicode character of classes Lu, Ll, Lt, Lm, Lo, or Nl>'
| '<A unicode_escape_sequence representing a character of classes Lu,
Ll, Lt, Lm, Lo, or Nl>'
combining_character
decimal_digit_character
connecting_character
formatting_character
Entre los ejemplos de identificadores válidos se incluyen " identifier1 ", " _identifier2
" y " @if ".
El prefijo " @ " permite el uso de palabras clave como identificadores, lo que resulta útil
al interactuar con otros lenguajes de programación. El carácter @ no es realmente parte
del identificador, por lo que el identificador podría verse en otros idiomas como un
identificador normal, sin el prefijo. Un identificador con un @ prefijo se denomina
identificador textual. Se permite el uso del @ prefijo para los identificadores que no son
palabras clave, pero se desaconseja encarecidamente como una cuestión de estilo.
El ejemplo:
C#
class @class
if (@bool)
System.Console.WriteLine("true");
else
System.Console.WriteLine("false");
class Class1
cl\u0061ss.st\u0061tic(true);
define una clase denominada " class " con un método estático denominado " static "
que toma un parámetro con el nombre " bool ". Tenga en cuenta que, puesto que no se
permiten los escapes de Unicode en palabras clave, el token " cl\u0061ss " es un
identificador y es el mismo identificador que " @class ".
Palabras clave
Una palabra clave es una secuencia similar a un identificador de caracteres que está
reservada y no se puede usar como identificador excepto cuando está precedida por el
@ carácter.
antlr
keyword
| 'volatile' | 'while'
Literales
Un literal es una representación de código fuente de un valor.
antlr
literal
: boolean_literal
| integer_literal
| real_literal
| character_literal
| string_literal
| null_literal
booleanos, literales
antlr
boolean_literal
: 'true'
| 'false'
Literales enteros
Los literales enteros se utilizan para escribir valores de tipos int , uint , long y ulong .
Los literales enteros tienen dos formas posibles: decimal y hexadecimal.
antlr
integer_literal
: decimal_integer_literal
| hexadecimal_integer_literal
;
decimal_integer_literal
: decimal_digit+ integer_type_suffix?
decimal_digit
: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
integer_type_suffix
: 'U' | 'u' | 'L' | 'l' | 'UL' | 'Ul' | 'uL' | 'ul' | 'LU' | 'Lu' | 'lU'
| 'lu'
hexadecimal_integer_literal
hex_digit
: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
| 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f';
Si el valor representado por un literal entero está fuera del intervalo del ulong tipo, se
produce un error en tiempo de compilación.
Como cuestión de estilo, se sugiere que " L " se use en lugar de " l " cuando se
escriben literales de tipo long , ya que es fácil confundir la letra " l " con el dígito " 1 ".
Para permitir que los valores y más pequeños posibles int long se escriban como
literales enteros decimales, existen las dos reglas siguientes:
Cuando un decimal_integer_literal con el valor 2147483648 (2 ^ 31) y no hay
ningún integer_type_suffix aparece como el token inmediatamente después de un
token de operador unario menos (operador unario menos), el resultado es una
constante de tipo int con el valor-2147483648 (-2 ^ 31). En todas las demás
situaciones, tal decimal_integer_literal es de tipo uint .
Cuando un decimal_integer_literal con el valor 9223372036854775808 (2 ^ 63) y no
integer_type_suffix o el integer_type_suffix L o l aparece como el token
inmediatamente después de un token de operador unario menos (operador unario
menos), el resultado es una constante de tipo long con el valor-
9.223.372.036.854.775.808 (-2 ^ 63). En todas las demás situaciones, tal
decimal_integer_literal es de tipo ulong .
Literales reales
Los literales reales se utilizan para escribir valores de tipos float , double y decimal .
antlr
real_literal
| decimal_digit+ real_type_suffix
exponent_part
sign
: '+'
| '-'
real_type_suffix
Un literal real con sufijo F o f es de tipo float . Por ejemplo, los literales 1f ,
1.5f , 1e10f y 123.456F son de tipo float .
Un literal real con sufijo D o d es de tipo double . Por ejemplo, los literales 1d ,
1.5d , 1e10d y 123.456D son de tipo double .
Un literal real con sufijo M o m es de tipo decimal . Por ejemplo, los literales 1m ,
1.5m , 1e10m y 123.456M son de tipo decimal . Este literal se convierte en un
decimal valor mediante el uso del valor exacto, y, si es necesario, se redondea al
valor representable más cercano mediante el redondeo bancario (el tipo decimal).
Cualquier escala aparente en el literal se conserva a menos que el valor se
redondee o el valor sea cero (en cuyo caso, el signo y la escala serán 0). Por lo
tanto, el literal se 2.900m analizará para formar el decimal con el signo 0 , el
coeficiente 2900 y la escala 3 .
Tenga en cuenta que, en un literal real, siempre se requieren dígitos decimales después
del separador decimal. Por ejemplo, 1.3F es un literal real pero 1.F no es.
Literales de carácter
Un literal de carácter representa un carácter único y normalmente consta de un carácter
entre comillas, como en 'a' .
Nota: la notación gramatical ANTLR hace que la siguiente confusión. En ANTLR, cuando
escribe, \' representa una comilla simple ' . Y, cuando se escribe \\ , representa una
sola barra diagonal inversa \ . Por lo tanto, la primera regla para un literal de carácter
significa que empieza con una comilla simple, un carácter y, a continuación, una comilla
simple. Y las once secuencias de escape simples posibles son \' , \" , \\ , \0 , \a , \b
, \f , \n , \r , \t , \v .
antlr
character_literal
character
: single_character
| simple_escape_sequence
| hexadecimal_escape_sequence
| unicode_escape_sequence
single_character
simple_escape_sequence
hexadecimal_escape_sequence
Una secuencia de escape simple representa una codificación de caracteres Unicode, tal y
como se describe en la tabla siguiente.
\0 Null 0x0000
\a Alerta 0x0007
\b Retroceso 0x0008
Literales de cadena
C# admite dos formatos de literales de cadena: * literales de cadena normales _ y _
literales de cadena textuales *.
Un literal de cadena normal consta de cero o más caracteres entre comillas dobles,
como en "hello" , y puede incluir secuencias de escape simples (por ejemplo, \t para
el carácter de tabulación) y secuencias de escape hexadecimal y Unicode.
antlr
string_literal
: regular_string_literal
| verbatim_string_literal
regular_string_literal
regular_string_literal_character
: single_regular_string_literal_character
| simple_escape_sequence
| hexadecimal_escape_sequence
| unicode_escape_sequence
single_regular_string_literal_character
verbatim_string_literal
verbatim_string_literal_character
: single_verbatim_string_literal_character
| quote_escape_sequence
single_verbatim_string_literal_character
quote_escape_sequence
: '""'
compilación.
En el ejemplo
C#
string i = "one\r\ntwo\r\nthree";
string j = @"one
two
three";
Dado que una secuencia de escape hexadecimal puede tener un número variable de
dígitos hexadecimales, el literal de cadena "\x123" contiene un carácter único con el
valor hexadecimal 123. Para crear una cadena que contenga el carácter con el valor
hexadecimal 12 seguido del carácter 3, puede "\x00123" escribir "\x12" + "3" en su
lugar o.
Cada literal de cadena no tiene necesariamente como resultado una nueva instancia de
cadena. Cuando dos o más literales de cadena que son equivalentes de acuerdo con el
operador de igualdad de cadena (operadores de igualdad de cadena) aparecen en el
mismo programa, estos literales de cadena hacen referencia a la misma instancia de
cadena. Por ejemplo, la salida generada por
C#
class Test
object a = "hello";
object b = "hello";
System.Console.WriteLine(a == b);
se True debe a que los dos literales hacen referencia a la misma instancia de cadena.
Al igual que los literales de cadena, los literales de cadena interpolados pueden ser
normales o literales. Los literales de cadena normales interpolados están delimitados
por $" y " , y los literales de cadena textual interpolados están delimitados por $@" y "
.
Al igual que otros literales, el análisis léxico de un literal de cadena interpolada produce
inicialmente un token único, según la gramática siguiente. Sin embargo, antes del
análisis sintáctico, el token único de un literal de cadena interpolada se divide en varios
tokens para las partes de la cadena que los rodean, y los elementos de entrada que se
producen en los huecos se analizan léxicamente de nuevo. Esto puede, a su vez, generar
más literales de cadena interpolados que se van a procesar, pero, si léxicamente
correcto, dará lugar a una secuencia de tokens para que los procese el análisis
sintáctico.
antlr
interpolated_string_literal
: '$' interpolated_regular_string_literal
| '$' interpolated_verbatim_string_literal
interpolated_regular_string_literal
: interpolated_regular_string_whole
| interpolated_regular_string_start
interpolated_regular_string_literal_body interpolated_regular_string_end
interpolated_regular_string_literal_body
: regular_balanced_text
| interpolated_regular_string_literal_body
interpolated_regular_string_mid regular_balanced_text
interpolated_regular_string_whole
: '"' interpolated_regular_string_character* '"'
interpolated_regular_string_start
: '"' interpolated_regular_string_character* '{'
interpolated_regular_string_mid
: interpolation_format? '}'
interpolated_regular_string_characters_after_brace? '{'
interpolated_regular_string_end
: interpolation_format? '}'
interpolated_regular_string_characters_after_brace? '"'
interpolated_regular_string_characters_after_brace
: interpolated_regular_string_character_no_brace
| interpolated_regular_string_characters_after_brace
interpolated_regular_string_character
interpolated_regular_string_character
: single_interpolated_regular_string_character
| simple_escape_sequence
| hexadecimal_escape_sequence
| unicode_escape_sequence
| open_brace_escape_sequence
| close_brace_escape_sequence
;
interpolated_regular_string_character_no_brace
single_interpolated_regular_string_character
open_brace_escape_sequence
: '{{'
close_brace_escape_sequence
: '}}'
regular_balanced_text
: regular_balanced_text_part+
;
regular_balanced_text_part
: single_regular_balanced_text_character
| delimited_comment
| '@' identifier_or_keyword
| string_literal
| interpolated_string_literal
| '(' regular_balanced_text ')'
single_regular_balanced_text_character
interpolation_format
: ':' interpolation_format_character+
interpolation_format_character
interpolated_verbatim_string_literal
: interpolated_verbatim_string_whole
| interpolated_verbatim_string_start
interpolated_verbatim_string_literal_body interpolated_verbatim_string_end
interpolated_verbatim_string_literal_body
: verbatim_balanced_text
| interpolated_verbatim_string_literal_body
interpolated_verbatim_string_mid verbatim_balanced_text
interpolated_verbatim_string_whole
interpolated_verbatim_string_start
interpolated_verbatim_string_mid
: interpolation_format? '}'
interpolated_verbatim_string_characters_after_brace? '{'
interpolated_verbatim_string_end
: interpolation_format? '}'
interpolated_verbatim_string_characters_after_brace? '"'
interpolated_verbatim_string_characters_after_brace
: interpolated_verbatim_string_character_no_brace
| interpolated_verbatim_string_characters_after_brace
interpolated_verbatim_string_character
interpolated_verbatim_string_character
: single_interpolated_verbatim_string_character
| quote_escape_sequence
| open_brace_escape_sequence
| close_brace_escape_sequence
;
interpolated_verbatim_string_character_no_brace
single_interpolated_verbatim_string_character
verbatim_balanced_text
: verbatim_balanced_text_part+
verbatim_balanced_text_part
: single_verbatim_balanced_text_character
| comment
| '@' identifier_or_keyword
| string_literal
| interpolated_string_literal
| '(' verbatim_balanced_text ')'
single_verbatim_balanced_text_character
Ejemplos TODO
El literal null
antlr
null_literal
: 'null'
antlr
operator_or_punctuator
: '{' | '}' | '[' | ']' | '(' | ')' | '.' | ',' | ':' | ';'
| '+' | '-' | '*' | '/' | '%' | '&' | '|' | '^' | '!' | '~'
| '=' | '<' | '>' | '?' | '??' | '::' | '++' | '--' | '&&' | '||'
| '->' | '==' | '!=' | '<=' | '>=' | '+=' | '-=' | '*=' | '/=' | '%='
right_shift
: '>>'
right_shift_assignment
: '>>='
pp_directive
: pp_declaration
| pp_conditional
| pp_line
| pp_diagnostic
| pp_region
| pp_pragma
Una directiva de procesamiento previo siempre ocupa una línea de código fuente
independiente y siempre comienza con un # carácter y un nombre de directiva de
preprocesamiento. Los espacios en blanco pueden aparecer antes del # carácter y entre
el # carácter y el nombre de la Directiva.
Una línea de código fuente que contiene una #define #undef Directiva,, #if , #elif ,
#else , #endif , #line o #endregion puede terminar con un Comentario de una sola
C#
#define A
#undef B
class C
#if A
void F() {}
#else
void G() {}
#endif
#if B
void H() {}
#else
void I() {}
#endif
C#
class C
void F() {}
void I() {}
Por lo tanto, mientras que los dos programas son bastante diferentes y sintácticamente,
son idénticos.
antlr
conditional_symbol
antlr
pp_expression
pp_or_expression
: pp_and_expression
pp_and_expression
: pp_equality_expression
pp_equality_expression
: pp_unary_expression
pp_unary_expression
: pp_primary_expression
pp_primary_expression
: 'true'
| 'false'
| conditional_symbol
Directivas de declaración
Las directivas de declaración se utilizan para definir o anular la definición de símbolos
de compilación condicional.
antlr
pp_declaration
pp_new_line
Las #define #undef directivas y de un archivo de código fuente deben aparecer antes
del primer token (tokens) en el archivo de código fuente; de lo contrario, se produce un
error en tiempo de compilación. En términos intuitivos, #define y #undef las directivas
deben preceder a cualquier "código real" en el archivo de código fuente.
El ejemplo:
C#
#define Enterprise
#define Advanced
#endif
namespace Megacorp.Data
#if Advanced
#endif
es válido porque las #define directivas preceden al primer token (la namespace palabra
clave) en el archivo de código fuente.
C#
#define A
namespace N
#define B
#if B
class Class1 {}
#endif
#define Puede definir un símbolo de compilación condicional que ya esté definido, sin
C#
#define A
#define A
C#
#define A
#undef A
#undef A
antlr
pp_conditional
pp_if_section
pp_elif_section
pp_else_section:
pp_endif
conditional_section
: input_section
| skipped_section
skipped_section
: skipped_section_part+
skipped_section_part
: skipped_characters? new_line
| pp_directive
skipped_characters
not_number_sign
Los pp_expression s de las #if #elif directivas y se evalúan en orden hasta que se
produce una true . Si una expresión produce true , se selecciona la
conditional_section de la directiva correspondiente.
Si todos los pp_expression producen false y, si una #else Directiva está presente,
se selecciona el conditional_section de la #else Directiva.
De lo contrario, no se selecciona ningún conditional_section .
C#
class PurchaseTransaction
void Commit() {
#if Debug
CheckConsistency();
#if Trace
WriteToLog(this.ToString());
#endif
#endif
CommitHelper();
C#
class PurchaseTransaction
void Commit() {
#if Debug
CheckConsistency();
#else
/* Do something else
#endif
Sin embargo, tenga en cuenta que las directivas de procesamiento previo deben ser
léxicamente correctas incluso en secciones omitidas del código fuente.
C#
class Hello
System.Console.WriteLine(@"hello,
#if Debug
world
#else
Nebraska
#endif
");
Consola
hello,
#if Debug
world
#else
Nebraska
#endif
C#
#if X
/*
#else
/* */ class Q { }
#endif
antlr
pp_diagnostic
pp_message
: new_line
El ejemplo:
C#
#endif
siempre genera una advertencia ("se requiere una revisión del código antes de la
inserción en el repositorio") y genera un error en tiempo de compilación ("una
compilación no puede ser Debug and Retail") si se definen los símbolos condicionales
Debug y Retail . Tenga en cuenta que un pp_message puede contener texto arbitrario;
en concreto, no es necesario que los tokens sean correctos, tal y como se muestra en la
palabra comilla simple can't .
Directivas de región
Las directivas region se usan para marcar explícitamente las regiones del código fuente.
antlr
pp_region
pp_start_region
pp_end_region
No se adjunta ningún significado semántico a una región; las regiones están pensadas
para que las use el programador o las herramientas automatizadas para marcar una
sección del código fuente. El mensaje especificado en una #region Directiva o del
#endregion mismo modo no tiene ningún significado semántico; simplemente sirve para
identificar la región. La coincidencia #region de las #endregion directivas y puede tener
distintos pp_message.
C#
#region
...
#endregion
C#
#if true
...
#endif
Directivas de línea
Las directivas de línea se pueden usar para modificar los números de línea y los
nombres de archivo de origen que el compilador indica en la salida, como advertencias
y errores, y que se usan en los atributos de información de llamador (atributos de
información de llamador).
antlr
pp_line
line_indicator
| decimal_digit+
| 'default'
| 'hidden'
file_name
file_name_character
Una #line default Directiva invierte el efecto de todas las directivas de #line anteriores.
El compilador notifica la información de línea verdadera para las líneas siguientes,
exactamente como si no #line se hubieran procesado directivas.
Una #line hidden Directiva no tiene ningún efecto en el archivo y los números de línea
indicados en los mensajes de error, pero afecta a la depuración de nivel de origen. Al
depurar, todas las líneas entre una #line hidden Directiva y la #line Directiva
subsiguiente (que no es #line hidden ) no tienen información de número de línea. Al
recorrer el código en el depurador, estas líneas se omitirán por completo.
Directivas pragma
La #pragma Directiva de preprocesamiento se usa para especificar información
contextual opcional para el compilador. La información proporcionada en una #pragma
directiva nunca cambiará la semántica del programa.
antlr
pp_pragma
pragma_body
: pragma_warning_body
C# proporciona #pragma directivas para controlar las advertencias del compilador. Las
versiones futuras del lenguaje pueden incluir #pragma directivas adicionales. Para
garantizar la interoperabilidad con otros compiladores de C#, el compilador de
Microsoft C# no emite errores de compilación para las directivas desconocidas #pragma ;
sin embargo, estas directivas generan advertencias.
ADVERTENCIA de pragma
La #pragma warning Directiva se usa para deshabilitar o restaurar todo o un conjunto
determinado de mensajes de advertencia durante la compilación del texto del programa
subsiguiente.
antlr
pragma_warning_body
warning_action
: 'disable'
| 'restore'
warning_list
Una #pragma warning Directiva que omite la lista de advertencias afecta a todas las
advertencias. Una #pragma warning Directiva que incluye una lista de advertencias afecta
solo a las advertencias especificadas en la lista.
C#
using System;
class Program
[Obsolete]
Foo();
Conceptos básicos
Artículo • 16/09/2021 • Tiempo de lectura: 50 minutos
Inicio de la aplicación
Un ensamblado que tiene un punto de entrada* _ se denomina aplicación. Cuando se
ejecuta una aplicación, se crea un nuevo _ _ *dominio de aplicación**. Puede haber varias
instancias diferentes de una aplicación en el mismo equipo al mismo tiempo, y cada una
tiene su propio dominio de aplicación.
C#
Dado que C# admite la sobrecarga de métodos, una clase o un struct pueden contener
varias definiciones de algún método, siempre que cada una tenga una firma diferente.
Sin embargo, dentro de un único programa, ninguna clase o struct puede contener más
de un método denominado Main cuya definición lo califique como punto de entrada de
la aplicación. No obstante, se permiten otras versiones sobrecargadas de, Main siempre
que tengan más de un parámetro, o su único parámetro sea distinto del tipo string[] .
Una aplicación puede estar formada por varias clases o Structs. Es posible que más de
una de estas clases o Structs contengan un método denominado Main cuya definición
sea apta para su uso como punto de entrada de la aplicación. En estos casos, se debe
usar un mecanismo externo (como una opción del compilador de línea de comandos)
para seleccionar uno de estos Main métodos como punto de entrada.
En C#, cada método se debe definir como miembro de una clase o struct. Normalmente,
la accesibilidad declarada (accesibilidad declarada) de un método viene determinada
por los modificadores de acceso (modificadores de acceso) especificados en su
declaración y, de igual forma, la accesibilidad declarada de un tipo viene determinada
por los modificadores de acceso especificados en su declaración. Para que se pueda
llamar a un método dado de un tipo determinado, tanto el tipo como el miembro
deben ser accesibles. Sin embargo, el punto de entrada de la aplicación es un caso
especial. En concreto, el entorno de ejecución puede tener acceso al punto de entrada
de la aplicación, independientemente de su accesibilidad declarada y sin tener en
consideración la accesibilidad declarada de sus declaraciones de tipos envolventes.
En todos los demás aspectos, los métodos de punto de entrada se comportan como los
que no son puntos de entrada.
Finalización de aplicaciones
La finalización de la aplicación devuelve el control al entorno de ejecución.
Si el tipo de valor devuelto del método de punto de entrada es void , al alcanzar la llave
de } cierre () que finaliza ese método, o la ejecución de una return instrucción que no
tiene ninguna expresión, da como resultado un código de estado de finalización de 0 .
Antes de la finalización de una aplicación, se llama a los destructores para todos sus
objetos que todavía no se han recolectado como elemento no utilizado, a menos que se
haya suprimido dicha limpieza (por ejemplo, mediante una llamada al método de
biblioteca GC.SuppressFinalize ).
Declaraciones
Las declaraciones de un programa de C# definen los elementos constituyentes del
programa. Los programas de C# se organizan mediante espacios de nombres
(espaciosde nombres), que pueden contener declaraciones de tipos y declaraciones de
espacio de nombres anidadas. Las declaraciones de tipos (declaraciones de tipo) se
utilizan para definir clases (clases), Structs (Structs), interfaces (interfaces),
enumeraciones (enumeraciones) y delegados (delegados). Los tipos de miembros
permitidos en una declaración de tipo dependen del formulario de la declaración de
tipos. Por ejemplo, las declaraciones de clase pueden contener declaraciones de
constantes (constantes). campos (campos), métodos (métodos), propiedades
(propiedades), eventos (eventos), indexadores (indizadores), operadores (operadores),
constructores de instancias (constructores de instancias), constructores estáticos
(constructores estáticos), destructores (destructores) y tipos anidados (tipos anidados).
C#
namespace Megacorp.Data
class Customer
...
namespace Megacorp.Data
class Order
...
C#
class A
void F() {
int i = 0;
if (true) {
int i = 1;
void G() {
if (true) {
int i = 0;
int i = 1;
void H() {
if (true) {
int i = 0;
if (true) {
int i = 1;
void I() {
H();
H();
Miembros
Los espacios de nombres y los tipos tienen miembros. Los miembros de una entidad
están disponibles con carácter general a través del uso de un nombre completo que
empieza por una referencia a la entidad, seguido de un . token "", seguido del nombre
del miembro.
Los espacios de nombres y los tipos declarados dentro de un espacio de nombres son
miembros de ese espacio de nombres. Esto corresponde directamente a los nombres
declarados en el espacio de declaración del espacio de nombres.
Los miembros de un tipo simple se corresponden directamente con los miembros del
tipo de struct con alias del tipo simple:
Miembros de enumeración
Los miembros de una enumeración son las constantes declaradas en la enumeración y
los miembros heredados de la clase base directa de la enumeración System.Enum y las
clases base indirectas System.ValueType y object .
Miembros de clase
Los miembros de una clase son los miembros declarados en la clase y los miembros
heredados de la clase base (excepto para la clase object que no tiene clase base). Los
miembros heredados de la clase base incluyen las constantes, campos, métodos,
propiedades, eventos, indizadores, operadores y tipos de la clase base, pero no los
constructores de instancias, destructores y constructores estáticos de la clase base. Los
miembros de clase base se heredan sin tener en cuenta su accesibilidad.
Miembros de interfaz
Los miembros de una interfaz son los miembros declarados en la interfaz y en todas las
interfaces base de la interfaz. Los miembros de la clase object no son, estrictamente
hablando, miembros de cualquier interfaz (miembros de lainterfaz). Sin embargo, los
miembros de la clase object están disponibles a través de la búsqueda de miembros en
cualquier tipo de interfaz (búsqueda de miembros).
Miembros de la matriz
Los miembros de una matriz son los miembros heredados de la clase System.Array .
Miembros de delegado
Los miembros de un delegado son los miembros heredados de la clase System.Delegate
.
Acceso a miembros
Las declaraciones de miembros permiten el control sobre el acceso a miembros. La
accesibilidad de un miembro se establece mediante la accesibilidad declarada
(accesibilidad declarada) del miembro combinado con la accesibilidad del tipo
contenedor inmediato, si existe.
Accesibilidad declarada
La accesibilidad declarada de un miembro puede ser una de las siguientes:
Dependiendo del contexto en el que tenga lugar una declaración de miembro, solo se
permiten determinados tipos de accesibilidad declarada. Además, cuando una
declaración de miembro no incluye modificadores de acceso, el contexto en el que se
produce la declaración determina la accesibilidad declarada predeterminada.
Los miembros de clase pueden tener cualquiera de los cinco tipos de accesibilidad
declarada y tienen como valor predeterminado la private accesibilidad declarada.
(Tenga en cuenta que un tipo declarado como miembro de una clase puede tener
cualquiera de los cinco tipos de accesibilidad declarada, mientras que un tipo
declarado como miembro de un espacio de nombres solo puede tener public o
internal declarar accesibilidad).
Los miembros de struct pueden tener public , internal o la private accesibilidad
declarada y tienen como valor predeterminado la private accesibilidad declarada
porque los Structs están sellados implícitamente. Los miembros de estructura
introducidos en un struct (es decir, no heredados por ese struct) no pueden tener
protected o protected internal declarar accesibilidad. (Tenga en cuenta que un
tipo declarado como miembro de un struct puede tener public , internal o la
private accesibilidad declarada, mientras que un tipo declarado como miembro
Dominios de accesibilidad
El dominio de accesibilidad* de un miembro se compone de las secciones
(posiblemente disjuntos) del texto del programa en el que se permite el acceso al
miembro. A efectos de definir el dominio de accesibilidad de un miembro, se dice que
un miembro es de nivel superior si no se declara dentro de un tipo, y se dice que un
miembro está anidado si se declara dentro de otro tipo. Además, el texto del programa
de un programa se define como todo el texto del programa contenido en todos los
archivos de código fuente del programa, y el texto del programa de un tipo se define
como todo el texto del programa incluido en el _type_declaration * s de ese tipo
(incluidos, posiblemente, los tipos anidados dentro del tipo).
En el ejemplo
C#
public class A
internal class B
public class C
private class D
Como se describe en miembros, todos los miembros de una clase base, excepto los
constructores de instancias, destructores y constructores estáticos, los heredan los tipos
derivados. Esto incluye incluso miembros privados de una clase base. Sin embargo, el
dominio de accesibilidad de un miembro privado incluye solo el texto del programa del
tipo en el que se declara el miembro. En el ejemplo
C#
class A
int x;
b.x = 1; // Ok
class B: A
Además de estas formas de acceso, una clase derivada puede tener acceso a un
constructor de instancia protegido de una clase base en un constructor_initializer
(inicializadores de constructor).
En el ejemplo
C#
public class A
protected int x;
a.x = 1; // Ok
b.x = 1; // Ok
public class B: A
b.x = 1; // Ok
En el ejemplo
C#
class C<T>
protected T x;
dt.x = default(T);
di.x = 123;
ds.x = "test";
las tres asignaciones a x se permiten porque todas tienen lugar a través de instancias
de tipos de clase construidas a partir del tipo genérico.
Restricciones de accesibilidad
Varias construcciones del lenguaje C# requieren que un tipo sea al menos tan accesible
como miembro u otro tipo. Se dice que un tipo T es al menos tan accesible como
miembro o tipo M si el dominio de accesibilidad de T es un supraconjunto del dominio
de accesibilidad de M . En otras palabras, T es al menos tan accesible como M si T es
accesible en todos los contextos en los que M es accesible.
La clase base directa de un tipo de clase debe ser al menos igual de accesible que
el propio tipo de clase.
Las interfaces base explícitas de un tipo de interfaz deben ser al menos igual de
accesibles que el propio tipo de interfaz.
El tipo de valor devuelto y los tipos de parámetros de un tipo de delegado deben
ser al menos igual de accesibles que el propio tipo de delegado.
El tipo de una constante debe ser al menos igual de accesible que la propia
constante.
El tipo de un campo debe ser al menos igual de accesible que el propio campo.
El tipo de valor devuelto y los tipos de parámetros de un método deben ser al
menos igual de accesibles que el propio método.
El tipo de una propiedad debe ser al menos igual de accesible que la misma
propiedad.
El tipo de un evento debe ser al menos igual de accesible que el propio evento.
Los tipos de parámetro y el tipo de un indexador deben ser al menos igual de
accesibles que el propio indexador.
El tipo de valor devuelto y los tipos de parámetro de un operador deben ser al
menos igual de accesibles que el propio operador.
Los tipos de parámetro de un constructor de instancia deben ser al menos tan
accesibles como el propio constructor de instancia.
En el ejemplo
C#
class A {...}
C#
class A {...}
public class B
A F() {...}
Firmas y sobrecarga
Los métodos, los constructores de instancia, los indizadores y los operadores se
caracterizan por sus firmas:
Aunque out ref los modificadores de parámetro y se consideran parte de una firma,
los miembros declarados en un tipo único no pueden diferir en la firma únicamente en
ref y out . Se produce un error en tiempo de compilación si dos miembros se declaran
en el mismo tipo con firmas que serían iguales si todos los parámetros de ambos
métodos con out Modificadores se cambiaran a ref modificadores. Para otros
propósitos de coincidencia de la firma (por ejemplo, ocultar o reemplazar), ref y out se
consideran parte de la firma y no coinciden entre sí. (Esta restricción consiste en permitir
que los programas de C# se traduzcan fácilmente para ejecutarse en el Common
Language Infrastructure (CLI), que no proporciona una manera de definir métodos que
difieren únicamente en ref y out ).
En el caso de las firmas, los tipos object y dynamic se consideran iguales. Por lo tanto,
los miembros declarados en un tipo único pueden no diferir en la firma únicamente en
object y dynamic .
C#
interface ITest
Tenga en cuenta que todos los ref out modificadores de parámetro y (parámetros de
método) forman parte de una firma. Por lo tanto, F(int) y F(ref int) son firmas
únicas. Sin embargo, F(ref int) y F(out int) no se pueden declarar dentro de la
misma interfaz porque sus firmas difieren únicamente en ref y out . Además, tenga en
cuenta que el tipo de valor devuelto y el params modificador no forman parte de una
firma, por lo que no es posible sobrecargar únicamente en función del tipo de valor
devuelto o de la inclusión o exclusión del params modificador. Como tal, las
declaraciones de los métodos F(int) e F(params string[]) identificados anteriormente
provocan un error en tiempo de compilación.
Ámbitos
El *ámbito _ de un nombre es la región del texto del programa en la que es posible
hacer referencia a la entidad declarada por el nombre sin la calificación del nombre. Los
ámbitos se pueden anidar y un ámbito interno puede volver a declarar el significado de
un nombre desde un ámbito externo (sin embargo, no se quita la restricción impuesta
por las declaraciones que se encuentran dentro de un bloque anidado no es posible
declarar una variable local con el mismo nombre que una variable local en un bloque de
inclusión). A continuación, se dice que el nombre del ámbito externo es _ Hidden* en la
región del texto del programa que abarca el ámbito interno, y el acceso al nombre
exterior solo es posible mediante la calificación del nombre.
C#
class A
void F() {
i = 1;
int i = 0;
Dentro del ámbito de una variable local, se trata de un error en tiempo de compilación
para hacer referencia a la variable local en una posición textual que precede al
local_variable_declarator de la variable local. Por ejemplo
C#
class A
int i = 0;
void F() {
int i;
i = 2;
void G() {
void H() {
Las reglas de ámbito de las variables locales están diseñadas para garantizar que el
significado de un nombre usado en un contexto de expresión siempre es el mismo
dentro de un bloque. Si el ámbito de una variable local solo se extiende desde su
declaración hasta el final del bloque, en el ejemplo anterior, la primera asignación se
asignaría a la variable de instancia y la segunda asignación asignaría a la variable local,
lo que posiblemente provocaría errores en tiempo de compilación si las instrucciones
del bloque se reorganizaran posteriormente.
C#
using System;
class A {}
class Test
Ocultación de nombres
El ámbito de una entidad suele abarcar más texto del programa que el espacio de
declaración de la entidad. En concreto, el ámbito de una entidad puede incluir
declaraciones que introducen nuevos espacios de declaración que contienen entidades
con el mismo nombre. Dichas declaraciones hacen que la entidad original se convierta
en *Hidden _. Por el contrario, se dice que una entidad es _ visible* cuando no está
oculta.
En el ejemplo
C#
class A
int i = 0;
void F() {
int i = 1;
void G() {
i = 1;
dentro del F método, la variable de instancia i está oculta por la variable local i , pero
dentro del G método i todavía hace referencia a la variable de instancia.
C#
class Outer
class Inner
void G() {
F("Hello"); // Error
la llamada F(1) invoca el F declarado en Inner porque todas las repeticiones externas
de F están ocultas por la declaración interna. Por la misma razón, la llamada F("Hello")
produce un error en tiempo de compilación.
Las reglas que rigen las declaraciones de operador (operadores) hacen imposible que
una clase derivada declare un operador con la misma signatura que un operador en una
clase base. Por lo tanto, los operadores nunca se ocultan entre sí.
C#
class Base
C#
class Base
Una declaración de un nuevo miembro oculta un miembro heredado solo dentro del
ámbito del nuevo miembro.
C#
class Base
antlr
namespace_name
: namespace_or_type_name
type_name
: namespace_or_type_name
namespace_or_type_name
: identifier type_argument_list?
| qualified_alias_member
Nombres completos
Cada espacio de nombres y tipo tiene un nombre completo, que identifica de forma
única el espacio de nombres o el tipo entre todos los demás. El nombre completo de un
espacio de nombres o tipo N se determina de la siguiente manera:
C#
class A {} // A
namespace X // X
class B // X.B
class C {} // X.B.C
namespace Y // X.Y
class D {} // X.Y.D
class E {} // X.Y.E
Dado que el recolector de elementos no utilizados tiene una latitud ancha a la hora de
decidir cuándo recopilar objetos y ejecutar destructores, una implementación
compatible puede generar resultados que sean diferentes de los que se muestran en el
código siguiente. El programa
C#
using System;
class A
~A() {
class B
object Ref;
public B(object o) {
Ref = o;
~B() {
class Test
b = null;
GC.Collect();
GC.WaitForPendingFinalizers();
crea una instancia de la clase A y una instancia de la clase B . Estos objetos son válidos
para la recolección de elementos no utilizados cuando b se asigna el valor a la variable
null , ya que no es posible que ningún código escrito por el usuario tenga acceso a
ellos. El resultado puede ser
Consola
Destruct instance of A
Destruct instance of B
or
Consola
Destruct instance of B
Destruct instance of A
Dado que el lenguaje no impone ninguna restricción en el orden en el que los objetos
se recolectan como elementos no utilizados.
En casos sutiles, la distinción entre "candidato a la destrucción" y "puede ser válida para
la recopilación" puede ser importante. Por ejemplo,
C#
using System;
class A
~A() {
Console.WriteLine("A.F");
Test.RefA = this;
class B
public A Ref;
~B() {
Ref.F();
class Test
RefB.Ref = RefA;
RefB = null;
RefA = null;
GC.Collect();
GC.WaitForPendingFinalizers();
if (RefA != null)
Consola
Destruct instance of A
Destruct instance of B
A.F
Para evitar la confusión y el comportamiento inesperado, suele ser una buena idea que
los destructores solo realicen la limpieza en los datos almacenados en sus propios
campos del objeto y no realicen ninguna acción en los objetos a los que se hace
referencia ni en los campos estáticos.
Orden de ejecución
La ejecución de un programa de C# continúa de tal forma que los efectos secundarios
de cada subproceso en ejecución se conserven en puntos de ejecución críticos. Un
efecto secundario se define como una lectura o escritura de un campo volátil, una
escritura en una variable no volátil, una escritura en un recurso externo y el inicio de una
excepción. Los puntos de ejecución críticos en los que se debe conservar el orden de
estos efectos secundarios son las referencias a campos volátiles (campos volátiles), las
lock instrucciones (instrucción lock) y la creación y terminación de subprocesos. El
Los tipos del lenguaje C# se dividen en dos categorías principales: tipos de valor _ y
_tipos de referencia*.. Ambos tipos de valor y tipos de referencia pueden ser tipos
genéricos, que toman uno o varios parámetros de tipo _ * * *. Los parámetros de tipo
pueden designar tanto tipos de valor como tipos de referencia.
antlr
type
: value_type
| reference_type
| type_parameter
| type_unsafe
La categoría final de tipos, punteros, solo está disponible en código no seguro. Esto se
describe con más detalle en tipos de puntero.
Los tipos de valor se diferencian de los tipos de referencia en que las variables de los
tipos de valor contienen directamente sus datos, mientras que las variables de los tipos
de referencia almacenan referencias en sus datos, lo que se conoce como "objetos *". Con
los tipos de referencia, es posible que dos variables hagan referencia al mismo objeto y,
por lo tanto, las operaciones en una variable afecten al objeto al que hace referencia la
otra variable. Con los tipos de valor, cada variable tiene su propia copia de los datos y
no es posible que las operaciones en una afecten a la otra.
Tipos de valor
Un tipo de valor es un tipo de estructura o un tipo de enumeración. C# proporciona un
conjunto de tipos de struct predefinidos denominados tipos simples. Los tipos simples
se identifican mediante palabras reservadas.
antlr
value_type
: struct_type
| enum_type
struct_type
: type_name
| simple_type
| nullable_type
simple_type
: numeric_type
| 'bool'
numeric_type
: integral_type
| floating_point_type
| 'decimal'
integral_type
: 'sbyte'
| 'byte'
| 'short'
| 'ushort'
| 'int'
| 'uint'
| 'long'
| 'ulong'
| 'char'
floating_point_type
: 'float'
| 'double'
nullable_type
: non_nullable_value_type '?'
non_nullable_value_type
: type
enum_type
: type_name
La asignación a una variable de un tipo de valor crea una copia del valor que se va a
asignar. Esto difiere de la asignación a una variable de un tipo de referencia, que copia
la referencia pero no el objeto identificado por la referencia.
Constructores predeterminados
Todos los tipos de valor declaran implícitamente un constructor de instancia sin
parámetros público denominado *constructor predeterminado _. El constructor
predeterminado devuelve una instancia inicializada en cero, conocida como el valor _
default* para el tipo de valor:
C#
class A
void F() {
int i = 0;
Dado que cada tipo de valor tiene implícitamente un constructor de instancia sin
parámetros público, no es posible que un tipo de struct contenga una declaración
explícita de un constructor sin parámetros. Sin embargo, se permite que un tipo de
estructura declare constructores de instancia con parámetros (constructores).
Tipos de estructura
Un tipo de estructura es un tipo de valor que puede declarar constantes, campos,
métodos, propiedades, indizadores, operadores, constructores de instancias,
constructores estáticos y tipos anidados. La declaración de tipos de struct se describe en
declaraciones de struct.
Tipos simples
C# proporciona un conjunto de tipos de struct predefinidos denominados tipos simples.
Los tipos simples se identifican mediante palabras reservadas, pero estas palabras
reservadas son simplemente alias para los tipos de struct predefinidos en el System
espacio de nombres, tal como se describe en la tabla siguiente.
sbyte System.SByte
Palabra reservada Tipo con alias
byte System.Byte
short System.Int16
ushort System.UInt16
int System.Int32
uint System.UInt32
long System.Int64
ulong System.UInt64
char System.Char
float System.Single
double System.Double
bool System.Boolean
decimal System.Decimal
Dado que un tipo simple incluye un alias para un tipo de estructura, cada tipo simple
tiene miembros. Por ejemplo, int tiene los miembros declarados en System.Int32 y los
miembros heredados de System.Object , y se permiten las siguientes instrucciones:
C#
Los tipos simples se diferencian de otros tipos struct en que permiten determinadas
operaciones adicionales:
Tipos enteros
C# admite nueve tipos enteros: sbyte , byte , short , ushort , int , uint , long , ulong
y char . Los tipos enteros tienen los siguientes tamaños y rangos de valores:
El sbyte tipo representa enteros de 8 bits con signo con valores comprendidos
entre-128 y 127.
El byte tipo representa enteros de 8 bits sin signo con valores comprendidos entre
0 y 255.
El short tipo representa enteros de 16 bits con signo con valores comprendidos
entre-32768 y 32767.
El ushort tipo representa enteros de 16 bits sin signo con valores comprendidos
entre 0 y 65535.
El int tipo representa enteros de 32 bits con signo con valores comprendidos
entre-2147483648 y 2147483647.
El uint tipo representa enteros de 32 bits sin signo con valores comprendidos
entre 0 y 4294967295.
El long tipo representa enteros de 64 bits con signo con valores comprendidos
entre-9223372036854775808 y 9223372036854775807.
El ulong tipo representa enteros de 64 bits sin signo con valores comprendidos
entre 0 y 18446744073709551615.
El char tipo representa enteros de 16 bits sin signo con valores comprendidos
entre 0 y 65535. El conjunto de valores posibles para el tipo char corresponde al
juego de caracteres Unicode. Aunque char tiene la misma representación que
ushort , no todas las operaciones permitidas en un tipo se permiten en el otro.
Los operadores unarios y binarios de tipo entero siempre operan con una precisión de
32 bits con signo, una precisión de 32 bits sin signo, una precisión de 64 con signo o
una precisión de bit 64 sin signo:
El char tipo se clasifica como un tipo entero, pero difiere de los demás tipos enteros de
dos maneras:
Las constantes del char tipo deben escribirse como character_literal s o como
integer_literal s en combinación con una conversión al tipo char . Por ejemplo,
(char)10 es lo mismo que '\x000A' .
Los checked unchecked operadores and y las instrucciones se utilizan para controlar la
comprobación de desbordamiento de las operaciones aritméticas de tipo entero y las
conversiones (los operadores Checked y unchecked). En un checked contexto, un
desbordamiento produce un error en tiempo de compilación o provoca
System.OverflowException que se produzca una excepción. En un unchecked contexto,
se omiten los desbordamientos y se descartan los bits de orden superior que no caben
en el tipo de destino.
Cero positivo y cero negativo. En la mayoría de los casos, cero positivo y cero
negativo se comportan exactamente igual que el valor cero simple, pero ciertas
operaciones distinguen entre los dos (operador de división).
Infinito positivo y infinito negativo. Los infinitos se generan mediante operaciones
como la división por cero de un número distinto de cero. Por ejemplo, 1.0 / 0.0
produce infinito positivo y -1.0 / 0.0 produce infinito negativo.
El valor no numérico , a menudo abreviado como Nan. Los Nan se generan
mediante operaciones de punto flotante no válidas, como la división de cero por
cero.
El conjunto finito de valores distintos de cero del formulario s * m * 2^e , donde
s es 1 o-1, y m y e vienen determinados por el tipo de punto flotante
determinado: para float , 0 < m < 2^24 y -149 <= e <= 104 , y para double , 0 <
m < 2^53 y -1075 <= e <= 970 . Los números de punto flotante desnormalizados se
Las operaciones de punto flotante se pueden realizar con una precisión mayor que el
tipo de resultado de la operación. Por ejemplo, algunas arquitecturas de hardware
admiten un tipo de punto flotante "extendido" o "Long Double" con un intervalo y una
precisión mayores que el double tipo y realizan implícitamente todas las operaciones de
punto flotante con este tipo de precisión superior. Solo con un costo excesivo en el
rendimiento se pueden realizar estas arquitecturas de hardware para realizar
operaciones de punto flotante con menos precisión y, en lugar de requerir una
implementación para que se pierda rendimiento y precisión, C# permite usar un tipo de
precisión mayor para todas las operaciones de punto flotante. Aparte de ofrecer
resultados más precisos, esto rara vez tiene efectos medibles. Sin embargo, en las
expresiones con el formato x * y / z , donde la multiplicación genera un resultado que
está fuera del double intervalo, pero la división posterior devuelve el resultado temporal
al double intervalo, el hecho de que la expresión se evalúe en un formato de intervalo
más alto puede provocar que se genere un resultado finito en lugar de un infinito.
El tipo decimal
El tipo decimal es un tipo de datos de 128 bits adecuado para cálculos financieros y
monetarios. El decimal tipo puede representar valores comprendidos entre
1.0 * 10^-28 y aproximadamente 7.9 * 10^28 con 28-29 dígitos significativos.
El conjunto finito de valores de tipo decimal tiene el formato (-1)^s * c * 10^-e ,
donde el signo s es 0 o 1, el coeficiente c lo proporciona 0 <= *c* < 2^96 y la escala e
es tal que 0 <= e <= 28 . El decimal tipo no admite ceros con signo, infinitos o Nan. Un
decimal se representa como un entero de 96 bits escalado por una potencia de diez.
Para decimal s con un valor absoluto menor que 1.0m , el valor es exacto hasta la
posición decimal 28, pero no más. Para decimal s con un valor absoluto mayor o igual
que 1.0m , el valor es exacto a 28 o 29 dígitos. Al contrario que float los double tipos
de datos y, los números fraccionarios decimales como 0,1 se pueden representar
exactamente en la decimal representación. En las float double representaciones y,
estos números suelen ser fracciones infinitas, por lo que las representaciones son más
propensas a errores de redondeo.
El resultado de una operación con valores de tipo decimal es que resultaría de calcular
un resultado exacto (conservando la escala, tal y como se define para cada operador) y,
a continuación, redondear para ajustarse a la representación. Los resultados se
redondean al valor representable más cercano y, cuando un resultado está igualmente
cerca de dos valores representables, al valor que tiene un número par en la posición del
dígito menos significativo (esto se conoce como "redondeo bancario"). Un resultado de
cero siempre tiene un signo de 0 y una escala de 0.
Si una operación aritmética decimal produce un valor menor o igual que 5 * 10^-29 en
valor absoluto, el resultado de la operación se convierte en cero. Si una decimal
operación aritmética genera un resultado que es demasiado grande para el decimal
formato, System.OverflowException se produce una excepción.
El decimal tipo tiene mayor precisión pero menor que los tipos de punto flotante. Por lo
tanto, las conversiones de los tipos de punto flotante a decimal pueden producir
excepciones de desbordamiento, y las conversiones de decimal a los tipos de punto
flotante podrían provocar la pérdida de precisión. Por estos motivos, no existe ninguna
conversión implícita entre los tipos de punto flotante y decimal , y sin conversiones
explícitas, no es posible mezclar los operandos y el punto flotante decimal en la misma
expresión.
Tipo bool
El bool tipo representa las cantidades lógicas booleanas. Los valores posibles de tipo
bool son true y false .
No existe ninguna conversión estándar entre bool y otros tipos. En concreto, el bool
tipo es distinto e independiente de los tipos enteros, y bool no se puede usar un valor
en lugar de un valor entero, y viceversa.
En los lenguajes C y C++, un valor entero o de punto flotante cero, o un puntero nulo se
puede convertir al valor booleano false , y un valor entero distinto de cero o de punto
flotante, o un puntero no nulo se puede convertir al valor booleano true . En C#, estas
conversiones se realizan comparando explícitamente un valor entero o de punto
flotante en cero, o comparando explícitamente una referencia de objeto a null .
Tipos de enumeración
Un tipo de enumeración es un tipo distinto con constantes con nombre. Cada tipo de
enumeración tiene un tipo subyacente, que debe ser byte , sbyte , short , ushort , int
, uint long o ulong . El conjunto de valores del tipo de enumeración es el mismo que
el conjunto de valores del tipo subyacente. Los valores del tipo de enumeración no
están restringidos a los valores de las constantes con nombre. Los tipos de enumeración
se definen mediante declaraciones de enumeración (declaracionesde enumeración).
parámetro de tipo restringido para ser un tipo de valor que no acepta valores NULL (es
decir, cualquier parámetro de tipo con una struct restricción). El System.Nullable<T>
tipo especifica la restricción de tipo de valor para T (restricciones de parámetro de tipo),
lo que significa que el tipo subyacente de un tipo que acepta valores NULL puede ser
cualquier tipo de valor que no acepte valores NULL. El tipo subyacente de un tipo que
acepta valores NULL no puede ser un tipo que acepta valores NULL ni un tipo de
referencia. Por ejemplo, int?? y string? son tipos no válidos.
Una instancia de un tipo que acepta valores NULL T? tiene dos propiedades públicas de
solo lectura:
Una HasValue propiedad de tipo bool
Una Value propiedad de tipo T
Una instancia para la que HasValue es true se dice que no es NULL. Una instancia que
no es null contiene un valor conocido y Value devuelve ese valor.
Una instancia para la que HasValue es false se dice que es NULL. Una instancia null tiene
un valor sin definir. Al intentar leer la Value de una instancia null,
System.InvalidOperationException se produce una excepción. El proceso de acceso a la
Value propiedad de una instancia que acepta valores NULL se conoce como
desencapsulado.
Además del constructor predeterminado, todos los tipos que aceptan valores NULL T?
tienen un constructor público que toma un único argumento de tipo T . Dado un valor
x de tipo T , una invocación del constructor con el formato
C#
new T?(x)
Tipos de referencia
Un tipo de referencia es un tipo de clase, un tipo de interfaz, un tipo de matriz o un tipo
de delegado.
antlr
reference_type
: class_type
| interface_type
| array_type
| delegate_type
class_type
: type_name
| 'object'
| 'dynamic'
| 'string'
interface_type
: type_name
array_type
: non_array_type rank_specifier+
non_array_type
: type
rank_specifier
dim_separator
: ','
delegate_type
: type_name
Un valor de tipo de referencia es una referencia a una *instancia de del tipo, que se
conoce como un objeto _ * * *. El valor especial null es compatible con todos los tipos
de referencia e indica la ausencia de una instancia.
Tipos de clase
Un tipo de clase define una estructura de datos que contiene miembros de datos
(constantes y campos), miembros de función (métodos, propiedades, eventos,
indizadores, operadores, constructores de instancias, destructores y constructores
estáticos) y tipos anidados. Los tipos de clase admiten la herencia, un mecanismo por el
que las clases derivadas pueden extender y especializar clases base. Las instancias de
tipos de clase se crean mediante object_creation_expression s (expresiones de creación
de objetos).
Algunos tipos de clase predefinidos tienen un significado especial en el lenguaje C#, tal
y como se describe en la tabla siguiente.
System.Object Última clase base de todos los demás tipos. Vea el tipo de objeto.
System.ValueType Clase base de todos los tipos de valor. Vea el tipo System. ValueType.
El tipo de objeto
El object tipo de clase es la clase base definitiva de todos los demás tipos. Cada tipo de
C# deriva directa o indirectamente del object tipo de clase.
Tipo dynamic
El dynamic tipo, como object , puede hacer referencia a cualquier objeto. Cuando se
aplican operadores a expresiones de tipo dynamic , su resolución se aplaza hasta que se
ejecuta el programa. Por lo tanto, si el operador no se puede aplicar legalmente al
objeto al que se hace referencia, no se proporciona ningún error durante la compilación.
En su lugar, se producirá una excepción cuando se produzca un error en la resolución
del operador en tiempo de ejecución.
Tipo string
El string tipo es un tipo de clase sellado que hereda directamente de object . Las
instancias de la string clase representan cadenas de caracteres Unicode.
Los valores del string tipo pueden escribirse como literales de cadena (literales de
cadena).
Tipos de interfaz
Una interfaz define un contrato. Una clase o estructura que implementa una interfaz
debe adherirse a su contrato. Una interfaz puede heredar de varias interfaces base, y
una clase o estructura puede implementar varias interfaces.
Tipos de matriz
Una matriz es una estructura de datos que contiene cero o más variables a las que se
tiene acceso a través de índices calculados. Las variables contenidas en una matriz,
denominadas también elementos de la matriz, son todas del mismo tipo y este tipo se
conoce como tipo de elemento de la matriz.
Tipos delegados
Un delegado es una estructura de datos que hace referencia a uno o más métodos. En
el caso de los métodos de instancia, también hace referencia a sus instancias de objeto
correspondientes.
Conversiones Boxing
Una conversión boxing permite convertir implícitamente un value_type en un
reference_type. Existen las siguientes conversiones Boxing:
C#
T value;
public Box(T t) {
value = t;
C#
int i = 123;
object box = i;
C#
int i = 123;
Una clase Boxing como Box<T> la anterior no existe realmente y el tipo dinámico de un
valor de conversión boxing no es realmente un tipo de clase. En su lugar, un valor con
conversión boxing de tipo T tiene el tipo dinámico T y una comprobación de tipos
dinámicos mediante el is operador puede simplemente hacer referencia al tipo T . Por
ejemplo,
C#
int i = 123;
object box = i;
if (box is int) {
dará como resultado la cadena " Box contains an int " en la consola.
Una conversión boxing implica que se realice una copia del valor al que se va a aplicar la
conversión boxing. Esto es diferente de la conversión de un reference_type al tipo
object , en el que el valor sigue haciendo referencia a la misma instancia y simplemente
se considera como el tipo menos derivado object . Por ejemplo, dada la declaración
C#
struct Point
public int x, y;
this.x = x;
this.y = y;
C#
object box = p;
p.x = 20;
Console.Write(((Point)box).x);
Conversiones unboxing
Una conversión unboxing permite convertir un reference_type explícitamente en un
value_type. Existen las siguientes conversiones unboxing:
C#
int i = (int)box;
C#
int i = ((Box<int>)box).value;
Los tipos construidos también se pueden usar en expresiones como nombres simples
(nombres simples) o al obtener acceso a un miembro (acceso a miembros).
C#
namespace Widgets
namespace MyApplication
using Widgets;
class X
C#
class Outer<T>
Argumentos de tipo
Cada argumento de una lista de argumentos de tipo es simplemente un tipo.
antlr
type_argument_list
type_arguments
type_argument
: type
Cada tipo construido cerrado tiene su propio conjunto de variables estáticas, que no se
comparten con otros tipos construidos cerrados. Puesto que no existe un tipo abierto
en tiempo de ejecución, no hay variables estáticas asociadas a un tipo abierto. Dos tipos
construidos cerrados son del mismo tipo si se construyen a partir del mismo tipo
genérico sin enlazar y sus argumentos de tipo correspondientes son del mismo tipo.
Un tipo sin enlazar hace referencia a la entidad declarada por una declaración de tipos.
Un tipo genérico sin enlazar no es en sí mismo un tipo y no se puede usar como el tipo
de una variable, argumento o valor devuelto, o como un tipo base. La única
construcción en la que se puede hacer referencia a un tipo genérico sin enlazar es la
typeof expresión (el operador typeof).
Satisfacer restricciones
Siempre que se hace referencia a un tipo construido o a un método genérico, los
argumentos de tipo proporcionados se comprueban con las restricciones de parámetro
de tipo declaradas en el tipo o método genérico (restricciones de parámetro de tipo).
Para cada where cláusula, el argumento de tipo A que se corresponde con el parámetro
de tipo con nombre se compara con cada restricción de la manera siguiente:
predeterminados).
Dado que los parámetros de tipo no se heredan, las restricciones nunca se heredan. En
el ejemplo siguiente, D debe especificar la restricción en su parámetro de tipo T para
que T cumpla la restricción impuesta por la clase base B<T> . En cambio, E la clase no
necesita especificar una restricción, porque List<T> implementa IEnumerable para any
T .
C#
Parámetros de tipo
Un parámetro de tipo es un identificador que designa un tipo de valor o un tipo de
referencia al que está enlazado el parámetro en tiempo de ejecución.
antlr
type_parameter
: identifier
Dado que se pueden crear instancias de un parámetro de tipo con muchos argumentos
de tipo reales diferentes, los parámetros de tipo tienen operaciones y restricciones
ligeramente diferentes a las de otros tipos. Entre ellos, se incluye:
Un parámetro de tipo no se puede usar directamente para declarar una clase base
(clase base) o una interfaz (listas de parámetros de tipo variante).
Las reglas para la búsqueda de miembros en parámetros de tipo dependen de las
restricciones, si las hay, que se aplican al parámetro de tipo. Se detallan en la
búsqueda de miembros.
Las conversiones disponibles para un parámetro de tipo dependen de las
restricciones, si las hay, que se aplican al parámetro de tipo. Se detallan en
conversiones implícitas que implican parámetros de tipo y conversiones dinámicas
explícitas.
El literal null no se puede convertir en un tipo proporcionado por un parámetro
de tipo, excepto si se sabe que el parámetro de tipo es un tipo de referencia
(conversiones implícitas que implican parámetros de tipo). Sin embargo, default
en su lugar se puede utilizar una expresión (expresiones de valor predeterminado).
Además, un valor con un tipo proporcionado por un parámetro de tipo puede
compararse con null mediante == y != (operadores de igualdad de tipos
dereferencia), a menos que el parámetro de tipo tenga la restricción de tipo de
valor.
Una new expresión (expresiones de creación de objetos) solo se puede utilizar con
un parámetro de tipo si el parámetro de tipo está restringido por un
constructor_constraint o la restricción de tipo de valor (restricciones de parámetro
de tipo).
Un parámetro de tipo no se puede usar en ningún lugar dentro de un atributo.
No se puede usar un parámetro de tipo en un acceso de miembro (acceso a
miembros) o nombre de tipo (espacio de nombresy nombres de tipo) para
identificar un miembro estático o un tipo anidado.
En código no seguro, no se puede usar un parámetro de tipo como
unmanaged_type (tipos de puntero).
Como tipo, los parámetros de tipo son únicamente una construcción en tiempo de
compilación. En tiempo de ejecución, cada parámetro de tipo se enlaza a un tipo en
tiempo de ejecución que se especificó proporcionando un argumento de tipo a la
declaración de tipos genéricos. Por lo tanto, el tipo de una variable declarada con un
parámetro de tipo, en tiempo de ejecución, será un tipo construido cerrado (tipos
abiertos y cerrados). La ejecución en tiempo de ejecución de todas las instrucciones y
expresiones que impliquen parámetros de tipo usa el tipo real que se proporcionó
como argumento de tipo para ese parámetro.
Al igual que un tipo D de delegado, Expression<D> se dice que tiene tipos de parámetro
y de valor devuelto, que son los mismos que los de D .
En el ejemplo siguiente se representa una expresión lambda como código ejecutable y
como un árbol de expresión. Dado que existe una conversión a Func<int,int> , también
existe una conversión a Expression<Func<int,int>> :
C#
La definición exacta del tipo genérico, Expression<D> así como las reglas precisas para
construir un árbol de expresión cuando una expresión lambda se convierte en un tipo
de árbol de expresión, está fuera del ámbito de esta especificación.
delegado de tipo D :
C#
C#
int i1 = del(1);
int i2 = del2(1);
Categorías de variable
C# define siete categorías de variables: variables estáticas, variables de instancia,
elementos de matriz, parámetros de valor, parámetros de referencia, parámetros de
salida y variables locales. Las secciones siguientes describen cada una de estas
categorías.
En el ejemplo
C#
class A
int y;
int i = 1;
c = a + b++;
Variables estáticas
Un campo declarado con el static modificador se denomina variable estática. Una
variable estática entra en vigor antes de la ejecución del constructor
estático(constructores estáticos) para su tipo de contenido y deja de existir cuando el
dominio de aplicación asociado deja de existir.
Variables de instancia
Un campo declarado sin el static modificador se denomina variable de instancia.
Elementos de matriz
Los elementos de una matriz se crean cuando se crea una instancia de matriz y dejan de
existir cuando no hay referencias a esa instancia de matriz.
El valor inicial de cada uno de los elementos de una matriz es el valor predeterminado
(Valores predeterminados) del tipo de los elementos de la matriz.
Parámetros de valor
Un parámetro declarado sin un ref modificador o es un parámetro de out valor.
Parámetros de referencia
Un parámetro declarado con un ref modificador es un parámetro de referencia.
Parámetros de salida
Un parámetro declarado con un out modificador es un parámetro de salida.
Valores predeterminados
Las siguientes categorías de variables se inicializan automáticamente en sus valores
predeterminados:
Variables estáticas.
Variables de instancia de instancias de clase.
Elementos de matriz.
Asignación definitiva
En una ubicación determinada del código ejecutable de un miembro de función, se dice
que una variable se asigna definitivamente si el compilador puede demostrar, mediante
un análisis de flujo estático determinado(reglasprecisas para determinar la asignación
definitiva), que la variable se ha inicializado automáticamente o ha sido el destino de al
menos una asignación. De forma informal, las reglas de asignación definitiva son:
Variables estáticas.
Variables de instancia de instancias de clase.
Variables de instancia de variables de estructura asignadas inicialmente.
Elementos de matriz.
Parámetros de valor.
Parámetros de referencia.
Variables declaradas en una catch cláusula o una instrucción foreach .
El compilador procesa el cuerpo de cada miembro de función que tiene una o varias
variables inicialmente sin asignación. Para cada variable inicialmente sin asignación v, el
compilador determina un estado de asignación definitiva _ para _v en cada uno de los
puntos siguientes del miembro de función:
Asignado definitivamente. Esto indica que en todos los flujos de control posibles
hasta este punto, se ha asignado un valor a v.
No está asignado definitivamente. Para el estado de una variable al final de una
expresión de tipo , el estado de una variable que no está asignada definitivamente
puede (pero no necesariamente) entrar en uno de los siguientes bool subes
estados:
Se ha asignado definitivamente después de la expresión true. Este estado indica
que v se asigna definitivamente si la expresión booleana se evaluó como true,
pero no se asigna necesariamente si la expresión booleana se evaluó como
false.
Se ha asignado definitivamente después de la expresión false. Este estado indica
que v se asigna definitivamente si la expresión booleana se evaluó como false,
pero no se asigna necesariamente si la expresión booleana se evaluó como true.
Las reglas siguientes rigen cómo se determina el estado de una variable v en cada
ubicación.
Instrucciones de expresión
Para una instrucción de expresión stmt que consta de la expresión expr:
Instrucciones de declaración
Si stmt es una instrucción de declaración sin inicializadores, v tiene el mismo
estado de asignación definitiva en el punto final de stmt que al principio de stmt.
Si stmt es una instrucción de declaración con inicializadores, el estado de
asignación definido para v se determina como si stmt fuera una lista de
instrucciones, con una instrucción de asignación para cada declaración con un
inicializador (en el orden de declaración).
Instrucciones If
Para un if stmt de instrucción con el formato:
C#
Instrucciones switch
Instrucciones While
C#
Instrucciones Do
C#
Para instrucciones
Definición de la comprobación de for asignación para una instrucción del formulario:
C#
C#
for_initializer ;
while ( for_condition ) {
embedded_statement ;
for_iterator ;
Instrucciones Throw
C#
throw expr ;
Instrucciones Return
Para una instrucción stmt del formulario
C#
return expr ;
C#
return ;
Instrucciones Try-catch
C#
try try_block
catch(...) catch_block_1
...
catch(...) catch_block_n
Instrucciones Try-finally
Para una try instrucción stmt del formulario:
C#
Si se realiza una transferencia de flujo de control (por ejemplo, una instrucción ) que
comienza dentro de try_block y termina fuera de try_block , v también se considera
asignado definitivamente en esa transferencia de flujo de control si v se asigna
definitivamente al punto final de goto finally_block. (Esto no es solo si, si v se asigna
definitivamente por otro motivo en esta transferencia de flujo de control, se sigue
considerando asignado definitivamente).
Instrucciones Try-catch-finally
Definición del análisis de asignación try - catch - finally para una instrucción del
formulario:
C#
try try_block
catch(...) catch_block_1
...
catch(...) catch_block_n
finally *finally_block*
se hace como si la instrucción fuera try - finally una instrucción que incluye una try
- catch instrucción :
C#
try {
try try_block
catch(...) catch_block_1
...
catch(...) catch_block_n
finally finally_block
C#
class A
int i, j;
try {
goto LABEL;
i = 1;
// i definitely assigned
catch {
i = 3;
// i definitely assigned
finally {
j = 5;
// j definitely assigned
LABEL:;
// j definitely assigned
Instrucciones Foreach
Para un foreach stmt de instrucción con el formato :
C#
C#
Instrucciones lock
Para una lock instrucción stmt del formulario:
C#
Instrucciones Yield
C#
Cada una de estas expresiones tiene una o varias sub expressions que se evalúan
incondicionalmente en un orden fijo. Por ejemplo, el operador binario evalúa el lado
izquierdo del operador y, a % continuación, el lado derecho. Una operación de
indexación evalúa la expresión indizada y, a continuación, evalúa cada una de las
expresiones de índice, en orden de izquierda a derecha. Para una expresión expr, que
tiene sub expressions e1, e2, ..., eN, eN evaluadas en ese orden:
C#
C#
new type ( arg1 , arg2 , ... , argN )
En el ejemplo
C#
class A
int i;
// i definitely assigned
else {
instrucción incrustada, ya que podría haber probado false, lo que da lugar a que la
variable no i x >= 0 esté i asignada.
En el ejemplo
C#
class A
int i;
if (x >= 0 || (i = y) >= 0) {
else {
// i definitely assigned
instrucción incrustada, ya que podría haber probado true, lo que da lugar a que la
variable no i x >= 0 esté i asignada.
?: expresiones (condicionales)
Para un expr de expresión con el formato expr_cond ? expr_true : expr_false :
Funciones anónimas
En el ejemplo
C#
void F() {
int max;
max = 5;
DoWork(f);
C#
void F() {
int n;
D d = () => { n = 1; };
d();
Console.WriteLine(n);
Referencias de variables
Un variable_reference es una expresión que se clasifica como una variable. Un
variable_reference indica una ubicación de almacenamiento a la que se puede acceder
tanto para capturar el valor actual como para almacenar un nuevo valor.
antlr
variable_reference
: expression
C#
int a = 123;
Algunas conversiones están definidas por el lenguaje. Los programas también pueden
definir sus propias conversiones (conversiones definidas por el usuario).
Conversiones implícitas
Las conversiones siguientes se clasifican como conversiones implícitas:
Conversiones de identidad
Conversiones numéricas implícitas
Conversiones de enumeración implícitas
Conversiones de cadenas interpoladas IMPLÍCITAS
Conversiones implícitas que aceptan valores NULL
Conversiones de literales null
Conversiones de referencias implícitas
Conversiones Boxing
Conversiones dinámicas IMPLÍCITAS
Conversiones implícitas de expresiones constantes
Conversiones implícitas definidas por el usuario
Conversiones de función anónima
Conversiones de grupos de métodos
Conversión de identidad
Una conversión de identidad convierte de cualquier tipo al mismo tipo. Esta conversión
existe de manera que se puede decir que una entidad que ya tiene un tipo necesario se
puede convertir en ese tipo.
Las conversiones de int , uint , long o ulong a float y desde long o ulong a double
pueden provocar una pérdida de precisión, pero nunca producirán una pérdida de
magnitud. El resto de conversiones numéricas implícitas nunca pierden información.
No hay conversiones implícitas al char tipo, por lo que los valores de los otros tipos
enteros no se convierten automáticamente al char tipo.
La evaluación de una conversión implícita que acepta valores NULL en función de una
conversión subyacente de S a T continúa como sigue:
Las conversiones de referencia implícitas son las conversiones entre reference_type s que
se pueden demostrar que siempre se realizan correctamente y, por tanto, no requieren
comprobaciones en tiempo de ejecución.
Conversiones Boxing
Una conversión boxing permite que un value_type se convierta implícitamente en un
tipo de referencia. Existe una conversión boxing de cualquier non_nullable_value_type a
object and dynamic , a System.ValueType y a cualquier interface_type implementado por
Existe una conversión boxing de una nullable_type a un tipo de referencia, solo si existe
una conversión boxing del non_nullable_value_type subyacente al tipo de referencia.
Un tipo de valor tiene una conversión boxing a un tipo de interfaz I si tiene una
conversión boxing a un tipo de interfaz I0 y I0 tiene una conversión de identidad en I
.
Un tipo de valor tiene una conversión boxing a un tipo de interfaz I si tiene una
conversión boxing a un tipo de interfaz o delegado I0 y I0 se convierte en varianza
(conversión de varianza) en I .
C#
object o = "object"
dynamic d = "dynamic";
Conversiones explícitas
Las conversiones siguientes se clasifican como conversiones explícitas:
De double a sbyte , byte , short , ushort , int , uint , long , ulong , char ,
float o decimal .
De decimal a sbyte , byte , short , ushort , int , uint , long , ulong , char ,
float o double .
Dado que las conversiones explícitas incluyen todas las conversiones numéricas
implícitas y explícitas, siempre es posible convertir de cualquier numeric_type a cualquier
otra numeric_type mediante una expresión de conversión (expresiones de conversión).
De sbyte , byte , short , ushort , int , uint , long , ulong , char , float ,
double o decimal a cualquier enum_type.
Desde cualquier enum_type a sbyte , byte , short , ushort , int , uint , long ,
ulong ,,, char float double o decimal .
Una conversión de enumeración explícita entre dos tipos se procesa tratando cualquier
enum_type participante como el tipo subyacente de ese enum_type y, a continuación,
realizando una conversión numérica implícita o explícita entre los tipos resultantes. Por
ejemplo, dada una enum_type E con y el tipo subyacente de int , una conversión de E
a byte se procesa como una conversión numérica explícita (Conversiones numéricas
explícitas) de int a byte , y una conversión de byte a E se procesa como una
conversión numérica implícita (conversionesnuméricas implícitas) de byte a int .
La evaluación de una conversión que acepta valores NULL en función de una conversión
subyacente de S a T continúa como sigue:
Tenga en cuenta que un intento de desencapsular un valor que acepta valores null
producirá una excepción si el valor es null .
Las conversiones de referencia explícitas son esas conversiones entre los tipos de
referencia que requieren comprobaciones en tiempo de ejecución para asegurarse de
que son correctas.
Conversiones unboxing
Una conversión unboxing permite que un tipo de referencia se convierta explícitamente
en un value_type. Existe una conversión unboxing de los tipos object , dynamic y
System.ValueType en cualquier non_nullable_value_type y desde cualquier interface_type
a cualquier non_nullable_value_type que implemente el interface_type. Además,
System.Enum se puede aplicar la conversión unboxing a cualquier enum_type.
Un tipo S de valor tiene una conversión unboxing de un tipo de interfaz I si tiene una
conversión unboxing de un tipo de interfaz I0 y I0 tiene una conversión de identidad
en I .
Un tipo S de valor tiene una conversión unboxing de un tipo de interfaz I si tiene una
conversión unboxing de un tipo de interfaz o delegado, I0 y I0 se puede convertir en
varianza o ser convertible en I I I0 (conversión devarianza).
C#
class C
int i;
C#
object o = "1";
dynamic d = "2";
C#
class X<T>
class X<T>
Este código se compilará ahora pero la ejecución X<int>.F(7) producirá una excepción
en tiempo de ejecución, ya que una conversión boxing int no se puede convertir
directamente a long .
Conversiones estándar
Las conversiones estándar son las conversiones predefinidas que pueden producirse
como parte de una conversión definida por el usuario.
Las restricciones que se aplican a las conversiones definidas por el usuario se explican
en los operadores de conversión.
En primer lugar, si es necesario, realizar una conversión estándar del tipo de origen
al tipo de operando del operador de conversión definido por el usuario o de
elevación.
A continuación, se invoca el operador de conversión definido por el usuario o de
elevación para realizar la conversión.
Por último, si es necesario, realizar una conversión estándar del tipo de resultado
del operador de conversión definido por el usuario o de elevación al tipo de
destino.
respectivamente.
Busque el conjunto de tipos, D , del que se considerarán los operadores de
conversión definidos por el usuario. Este conjunto consta de S0 (si S0 es una clase
o struct), las clases base de S0 (si es S0 una clase), T0 (si T0 es una clase o
estructura) y las clases base de T0 (si T0 es una clase).
Busque el conjunto de operadores de conversión de elevación y definidos por el
usuario aplicables, U . Este conjunto consta de los operadores de conversión
implícitos y de elevación definidos por el usuario declarados por las clases o
Structs de D que se convierten de un tipo que engloba o engloba S a un tipo
englobado o englobado por T . Si U está vacío, la conversión no está definida y se
produce un error en tiempo de compilación.
Busque el tipo de origen más específico, SX , de los operadores en U :
Si alguno de los operadores de U convierte en S , entonces SX es S .
De lo contrario, si alguno de los operadores de U se convierte de tipos que
abarcan S , SX es el tipo más abarcado en el conjunto combinado de tipos de
origen de esos operadores. Si no se puede encontrar ningún tipo más abarcado,
la conversión es ambigua y se produce un error en tiempo de compilación.
De lo contrario, SX es el tipo más abarcado en el conjunto combinado de tipos
de origen de los operadores de U . Si no se encuentra exactamente un tipo más
englobado, la conversión es ambigua y se produce un error en tiempo de
compilación.
Busque el tipo de destino más específico, TX , de los operadores en U :
Si alguno de los operadores de U se convierte en T , entonces TX es T .
De lo contrario, si cualquiera de los operadores de U se convierte en tipos que
están englobados por T , TX es el tipo más abarcado en el conjunto combinado
de tipos de destino de esos operadores. Si no se encuentra exactamente un tipo
más englobado, la conversión es ambigua y se produce un error en tiempo de
compilación.
De lo contrario, TX es el tipo más abarcado en el conjunto combinado de tipos
de destino de los operadores de U . Si no se puede encontrar ningún tipo más
abarcado, la conversión es ambigua y se produce un error en tiempo de
compilación.
Busque el operador de conversión más específico:
Si U contiene exactamente un operador de conversión definido por el usuario
que convierte de SX a TX , este es el operador de conversión más específico.
De lo contrario, si U contiene exactamente un operador de conversión de
elevación que convierte de SX a TX , este es el operador de conversión más
específico.
De lo contrario, la conversión es ambigua y se produce un error en tiempo de
compilación.
Por último, aplique la conversión:
Si S no es SX , se realiza una conversión explícita estándar de S a SX .
Se invoca el operador de conversión definido por el usuario más específico para
convertir de SX a TX .
Si TX no es T , se realiza una conversión explícita estándar de TX a T .
Si F tiene una lista de parámetros con tipo explícito, cada parámetro de D tiene el
mismo tipo y modificadores que el parámetro correspondiente de F .
Si F tiene una lista de parámetros con tipo implícito, D no tiene ref out
parámetros o.
Si el cuerpo de F es una expresión y D tiene un void tipo de valor devuelto o F es
asincrónico D y tiene el tipo de valor devuelto Task , cuando cada parámetro de F
recibe el tipo del parámetro correspondiente en D , el cuerpo de F es una
expresión válida (WRT expresiones) que se permitiría como statement_expression
(instrucciones de expresión).
Si el cuerpo de F es un bloque de instrucciones y D tiene un void tipo de valor
devuelto o F es asincrónico y D tiene el tipo de valor devuelto Task , cuando cada
parámetro de F recibe el tipo del parámetro correspondiente en D , el cuerpo de
F es un bloque de instrucciones válido (WRT bloques) en el que ninguna return
Por motivos de brevedad, en esta sección se usa la forma abreviada para los tipos de
tarea Task y Task<T> (funciones asincrónicas).
C#
delegate R Func<A,R>(A arg);
En las asignaciones
C#
Func<int,int> f1 = x => x + 1; // Ok
Func<int,double> f2 = x => x + 1; // Ok
C#
class Test
return result;
...
Dado que los dos delegados de función anónimos tienen el mismo conjunto (vacío) de
variables externas capturadas y como las funciones anónimas son idénticas
semánticamente, el compilador puede hacer que los delegados hagan referencia al
mismo método de destino. En realidad, el compilador puede devolver la misma
instancia de delegado de ambas expresiones de función anónimas.
Ejemplo de implementación
En esta sección se describe una posible implementación de conversiones de funciones
anónimas en términos de otras construcciones de C#. La implementación que se
describe aquí se basa en los mismos principios utilizados por el compilador de Microsoft
C#, pero no es una implementación asignada, ni es lo único posible. Solo se mencionan
brevemente las conversiones a los árboles de expresión, ya que su semántica exacta está
fuera del ámbito de esta especificación.
C#
La forma más sencilla de una función anónima es aquella que no captura variables
externas:
C#
class Test
D d = () => { Console.WriteLine("test"); };
Esto se puede traducir a una creación de instancias de delegado que hace referencia a
un método estático generado por el compilador en el que se coloca el código de la
función anónima:
C#
class Test
D d = new D(__Method1);
Console.WriteLine("test");
C#
class Test
int x;
void F() {
D d = () => { Console.WriteLine(x); };
C#
class Test
int x;
void F() {
D d = new D(__Method1);
void __Method1() {
Console.WriteLine(x);
C#
class Test
void F() {
int y = 123;
D d = () => { Console.WriteLine(y); };
C#
class Test
void F() {
__locals1.y = 123;
D d = new D(__locals1.__Method1);
class __Locals1
public int y;
Console.WriteLine(y);
}
Por último, la función anónima siguiente captura this y dos variables locales con
distintas duraciones:
C#
class Test
int x;
void F() {
int y = 123;
int z = i * 2;
En este caso, se crea una clase generada por el compilador para cada bloque de
instrucciones en el que se capturan las variables locales de forma que las variables
locales de los diferentes bloques puedan tener duraciones independientes. Una
instancia de __Locals2 , la clase generada por el compilador para el bloque de
instrucciones interno, contiene la variable local z y un campo que hace referencia a una
instancia de __Locals1 . Una instancia de __Locals1 , la clase generada por el
compilador para el bloque de instrucciones exterior, contiene la variable local y y un
campo que hace referencia this al miembro de función envolvente. Con estas
estructuras de datos es posible llegar a todas las variables externas capturadas a través
de una instancia de __Local2 , y el código de la función anónima se puede implementar
como un método de instancia de esa clase.
C#
class Test
void F() {
__locals1.__this = this;
__locals1.y = 123;
__locals2.__locals1 = __locals1;
__locals2.z = i * 2;
D d = new D(__locals2.__Method1);
class __Locals1
public int y;
class __Locals2
public int z;
También se puede usar la misma técnica que se aplica aquí para capturar variables
locales al convertir funciones anónimas en árboles de expresión: las referencias a los
objetos generados por el compilador se pueden almacenar en el árbol de expresión y el
acceso a las variables locales se puede representar como accesos de campo en estos
objetos. La ventaja de este enfoque es que permite compartir las variables locales
"levantadas" entre los delegados y los árboles de expresión.
C#
class Test
D1 d1 = F; // Ok
D2 d2 = F; // Ok
C#
C#
Clasificaciones de expresiones
Una expresión se clasifica de las siguientes formas:
(el operador typeof). En cualquier otro contexto, una expresión clasificada como un
tipo genera un error en tiempo de compilación.
Un grupo de métodos, que es un conjunto de métodos sobrecargados que
resultan de una búsqueda de miembros (búsqueda de miembros). Un grupo de
métodos puede tener una expresión de instancia asociada y una lista de
argumentos de tipo asociada. Cuando se invoca un método de instancia, el
resultado de evaluar la expresión de instancia se convierte en la instancia
representada por this (este acceso). Se permite un grupo de métodos en un
invocation_expression (expresiones de invocación), un delegate_creation_expression
(expresiones de creación de delegado) y como el lado izquierdo de un operador is
y se puede convertir implícitamente en un tipo de delegado compatible
(conversiones de grupo de métodos). En cualquier otro contexto, una expresión
clasificada como grupo de métodos produce un error en tiempo de compilación.
Un literal null. Una expresión con esta clasificación se puede convertir
implícitamente a un tipo de referencia o a un tipo que acepta valores NULL.
Una función anónima. Una expresión con esta clasificación se puede convertir
implícitamente en un tipo de delegado compatible o un tipo de árbol de
expresión.
Un acceso de propiedad. Cada acceso de propiedad tiene un tipo asociado, es
decir, el tipo de la propiedad. Además, un acceso de propiedad puede tener una
expresión de instancia asociada. Cuando se invoca un descriptor get set de
acceso (el bloque o) de una propiedad de instancia, el resultado de evaluar la
expresión de instancia se convierte en la instancia representada por this (este
acceso).
Un acceso de evento. Cada acceso a eventos tiene un tipo asociado, es decir, el
tipo del evento. Además, un acceso a eventos puede tener una expresión de
instancia asociada. Un acceso a eventos puede aparecer como el operando
izquierdo de += los -= operadores y (asignación de eventos). En cualquier otro
contexto, una expresión clasificada como acceso de evento produce un error en
tiempo de compilación.
Un acceso de indexador. Cada acceso de indexador tiene un tipo asociado, es
decir, el tipo de elemento del indexador. Además, un acceso de indexador tiene
una expresión de instancia asociada y una lista de argumentos asociada. Cuando
se invoca un descriptor get set de acceso (el bloque o) de un acceso de
indexador, el resultado de evaluar la expresión de instancia se convierte en la
instancia representada por this (este acceso) y el resultado de evaluar la lista de
argumentos se convierte en la lista de parámetros de la invocación.
Nada. Esto sucede cuando la expresión es una invocación de un método con un
tipo de valor devuelto de void . Una expresión clasificada como Nothing solo es
válida en el contexto de una statement_expression (instrucciones de expresión).
Sin embargo, si una expresión es una expresión dinámica (es decir, tiene el tipo dynamic
), esto indica que cualquier enlace en el que participe debería basarse en su tipo en
tiempo de ejecución (es decir, el tipo real del objeto que denota en tiempo de
ejecución) en lugar del tipo que tiene en tiempo de compilación. Por consiguiente, el
enlace de esta operación se aplaza hasta el momento en que se ejecuta la operación
durante la ejecución del programa. Esto se conoce como enlace dinámico.
Cuando una operación está enlazada dinámicamente, el compilador realiza poca o
ninguna comprobación. En su lugar, si se produce un error en el enlace en tiempo de
ejecución, los errores se registran como excepciones en tiempo de ejecución.
Tiempo de enlace
El enlace estático se realiza en tiempo de compilación, mientras que el enlace dinámico
tiene lugar en tiempo de ejecución. En las secciones siguientes, el término tiempo de
enlace hace referencia a un tiempo de compilación o a un tiempo de ejecución, en
función de cuándo tenga lugar el enlace.
C#
object o = 5;
dynamic d = 5;
Enlace dinámico
El propósito de los enlaces dinámicos es permitir que los programas de C# interactúen
con objetos dinámicos, es decir, los objetos que no siguen las reglas normales del
sistema de tipos de c#. Los objetos dinámicos pueden ser objetos de otros lenguajes de
programación con distintos sistemas de tipos, o bien pueden ser objetos que se
configuran mediante programación para implementar su propia semántica de enlace
para diferentes operaciones.
En las secciones siguientes se describe para cada construcción del lenguaje exactamente
cuando se aplica el enlace dinámico, qué comprobación del tiempo de compilación se
aplica, en caso de que se aplique, y cuál es el resultado en tiempo de compilación y la
clasificación de la expresión.
ejecución.
Expresión constituyente cuyo tipo en tiempo de compilación es un parámetro de
tipo se considera que tiene el tipo al que está enlazado el parámetro de tipo en
tiempo de ejecución.
De lo contrario, se considera que la expresión Constituyente tiene su tipo en
tiempo de compilación.
Operadores
Las expresiones se construyen a partir de los operadores*operandos _ y. Los operadores
de una expresión indican qué operaciones se aplican a los operandos. Ejemplos de
operadores incluyen + , - , _ , / y new . Algunos ejemplos de operandos son literales,
campos, variables locales y expresiones.
Expresiones principales Principal x.y f(x) a[x] x++ x-- new typeof default
checked unchecked delegate
Sobrecarga de operadores
Todos los operadores unarios y binarios tienen implementaciones predefinidas que
están disponibles automáticamente en cualquier expresión. Además de las
implementaciones predefinidas, las implementaciones definidas por el usuario se
pueden introducir incluyendo operator declaraciones en clases y Structs (operadores).
Las implementaciones de operador definidas por el usuario siempre tienen prioridad
sobre las implementaciones de operadores predefinidas: solo cuando no existan
implementaciones de operadores definidos por el usuario aplicables, se tendrán en
cuenta las implementaciones de operadores predefinidas, como se describe en
resolución de sobrecargas de operador unario y resolución de sobrecarga de
operadores binarios.
+ - ! ~ ++ -- true false
C#
op x operator op(x)
x op operator op(x)
x op y operator op(x,y)
Las declaraciones de operador definidas por el usuario siempre requieren que al menos
uno de los parámetros sea del tipo de clase o estructura que contiene la declaración de
operador. Por lo tanto, no es posible que un operador definido por el usuario tenga la
misma firma que un operador predefinido.
Aunque es posible que un operador definido por el usuario realice cualquier cálculo, se
desaconsejan las implementaciones que generan resultados distintos de los que se
esperaban de forma intuitiva. Por ejemplo, una implementación de operator == debería
comparar los dos operandos para determinar si son iguales y devolver un bool
resultado adecuado.
Promociones numéricas
La promoción numérica consiste en realizar automáticamente determinadas
conversiones implícitas de los operandos de los operadores binarios y unarios
predefinidos. La promoción numérica no es un mecanismo distinto, sino un efecto de
aplicar la resolución de sobrecarga a los operadores predefinidos. La promoción
numérica específicamente no afecta a la evaluación de operadores definidos por el
usuario, aunque se pueden implementar operadores definidos por el usuario para
mostrar efectos similares.
C#
Tenga en cuenta que la primera regla no permite ninguna operación que mezcle el
decimal tipo con double los float tipos y. La regla sigue el hecho de que no hay
Tenga en cuenta también que no es posible que un operando sea de tipo ulong cuando
el otro operando es de un tipo entero con signo. La razón es que no existe ningún tipo
entero que pueda representar el intervalo completo de ulong , así como los tipos
enteros con signo.
En los dos casos anteriores, se puede usar una expresión de conversión para convertir
explícitamente un operando en un tipo que sea compatible con el otro operando.
En el ejemplo
C#
C#
Operadores de elevación
Los operadores de elevación permiten a los operadores predefinidos y definidos por el
usuario que operan en tipos de valor que no aceptan valores NULL usarse también con
formas que aceptan valores NULL de esos tipos. Los operadores de elevación se
construyen a partir de operadores predefinidos y definidos por el usuario que cumplen
ciertos requisitos, como se describe a continuación:
C#
+ ++ - -- ! ~
C#
C#
== !=
existe una forma de elevación de un operador si los tipos de operando son tipos
de valor que no aceptan valores NULL y si el tipo de resultado es bool . La forma
de elevación se construye agregando un único ? modificador a cada tipo de
operando. El operador de elevación considera que dos valores NULL son iguales y
un valor NULL es distinto de cualquier valor distinto de NULL. Si ambos operandos
no son NULL, el operador de elevación desencapsula los operandos y aplica el
operador subyacente para generar el bool resultado.
C#
< > <= >=
existe una forma de elevación de un operador si los tipos de operando son tipos
de valor que no aceptan valores NULL y si el tipo de resultado es bool . La forma
de elevación se construye agregando un único ? modificador a cada tipo de
operando. El operador de elevación produce el valor false si uno o los dos
operandos son NULL. De lo contrario, el operador de elevación desencapsula los
operandos y aplica el operador subyacente para generar el bool resultado.
Búsqueda de miembros
Una búsqueda de miembros es el proceso por el que se determina el significado de un
nombre en el contexto de un tipo. Una búsqueda de miembros se puede producir como
parte de la evaluación de una simple_name (nombres simples) o un member_access
(acceso a miembros) en una expresión. Si el simple_name o member_access se produce
como primary_expression de un invocation_expression (invocaciones de método), se dice
que el miembro se invoca.
Tipos base
En lo que respecta a la búsqueda de miembros, T se considera que un tipo tiene los
siguientes tipos base:
Miembros de función
Los miembros de función son miembros que contienen instrucciones ejecutables. Los
miembros de función siempre son miembros de tipos y no pueden ser miembros de
espacios de nombres. C# define las siguientes categorías de miembros de función:
Métodos
Propiedades
Events
Indizadores
Operadores definidos por el usuario
Constructores de instancias
Constructores estáticos
Destructores
antlr
argument_list
argument
: argument_name? argument_value
argument_name
: identifier ':'
argument_value
: expression
| 'ref' variable_reference
| 'out' variable_reference
Una argument_list está formada por uno o más argumentos, separados por comas. Cada
argumento consta de un argument_name opcional seguido de un argument_value. Un
argumento con un argument_name se conoce como un argumento *con nombre,
mientras que un argumento * sin un argument_name es un argumento posicional*. Es un
error que un argumento posicional aparezca después de un argumento con nombre en
un _argument_list *.
Una expresión, que indica que el argumento se pasa como parámetro de valor
(parámetros de valor).
Palabra clave ref seguida de un variable_reference (referencias de variable), que
indica que el argumento se pasa como parámetro de referencia (parámetros de
referencia). Una variable debe estar asignada definitivamente (asignación
definitiva) antes de que se pueda pasar como un parámetro de referencia. Palabra
clave out seguida de un variable_reference (referencias de variable), que indica que
el argumento se pasa como parámetro de salida (parámetros de salida). Una
variable se considera asignada definitivamente (asignación definitiva) después de
una invocación de miembro de función en la que se pasa la variable como
parámetro de salida.
Parámetros correspondientes
Para cada argumento de una lista de argumentos debe haber un parámetro
correspondiente en el miembro de función o el delegado que se va a invocar.
En el caso de los métodos virtuales y los indizadores definidos en las clases, la lista
de parámetros se elige de la declaración o invalidación más específica del
miembro de función, empezando por el tipo estático del receptor y buscando en
sus clases base.
En el caso de los métodos e indexadores de la interfaz, la lista de parámetros se
selecciona como la definición más específica del miembro, empezando por el tipo
de interfaz y buscando en las interfaces base. Si no se encuentra ninguna lista de
parámetros única, se construye una lista de parámetros con nombres inaccesibles y
ningún parámetro opcional, de modo que las invocaciones no pueden usar
parámetros con nombre u omitir argumentos opcionales.
Para los métodos parciales, se usa la lista de parámetros de la declaración de
método parcial de definición.
En el caso de todos los demás miembros de función y delegados, solo hay una
lista de parámetros única, que es la que se usa.
class Test
int i = 0;
genera el resultado
Consola
x = 0, y = 1, z = 2
x = 4, y = -1, z = 3
C#
class Test
F(ref a[0]); // Ok
C#
C#
F(10, 20);
C#
En concreto, tenga en cuenta que se crea una matriz vacía cuando no hay ningún
argumento dado para la matriz de parámetros.
Inferencia de tipos
Cuando se llama a un método genérico sin especificar argumentos de tipo, un proceso
de inferencia de tipos intenta deducir los argumentos de tipo de la llamada. La
presencia de la inferencia de tipos permite usar una sintaxis más cómoda para llamar a
un método genérico y permite al programador evitar especificar información de tipos
redundantes. Por ejemplo, dada la declaración del método:
C#
class Chooser
C#
C#
La inferencia de tipos tiene lugar en fases. Cada fase intentará deducir los argumentos
de tipo para obtener más variables de tipo basadas en los resultados de la fase anterior.
La primera fase realiza algunas inferencias iniciales de límites, mientras que en la
segunda fase se corrigen las variables de tipo a tipos específicos y se deducen más
límites. Es posible que la segunda fase se repita varias veces.
Nota: La inferencia de tipos no solo tiene lugar cuando se llama a un método genérico.
La inferencia de tipos para la conversión de grupos de métodos se describe en
inferencia de tipos para la conversión de grupos de métodos y encontrar el mejor tipo
común de un conjunto de expresiones se describe en Buscar el mejor tipo común de un
conjunto de expresiones.
La primera fase
La segunda fase
Tipos de entrada
Si E es un grupo de métodos o una función anónima con tipos implícitos y T es un tipo
de delegado o un tipo de árbol de expresión, todos los tipos de parámetro de T son
tipos de entrada de tipo E T .
Tipos de salida
Si E es un grupo de métodos o una función anónima y T es un tipo de delegado o un
tipo de árbol de expresión, el tipo de valor devuelto de T es un tipo de salida de E con
el tipo T .
Dependencia
Una variable de tipo sin fijo Xi depende directamente de una variable de tipo sin corregir
Xj si para algún argumento Ek con tipo Tk Xj se produce en un tipo de entrada de Ek
Una inferencia de tipo de parámetro explícita se realiza desde una expresión E a un tipo
T de la siguiente manera:
Si E es una función anónima con tipo explícito con tipos de parámetro U1...Uk y
T es un tipo de delegado o un tipo de árbol de expresión con tipos de parámetro
V1...Vk , para cada Ui se realiza una inferencia exacta (inferencias exactas) desde
Ui hasta el Vi correspondiente
Inferencias exactas
rango
V es el tipo V1? y U es el tipo. U1?
Si se aplica cualquiera de estos casos, se realiza una inferencia exacta desde cada
Ui hasta el correspondiente Vi .
parámetro de tipo cuyo tipo base efectivo es U1[...] ) del mismo rango
Si se aplica cualquiera de estos casos, se realiza una inferencia desde cada Ui hasta
el correspondiente, como se indica a continuación Vi :
Si Ui no se sabe que es un tipo de referencia, se realiza una inferencia exacta .
De lo contrario, si U es un tipo de matriz, se realiza una inferencia de límite
inferior .
De lo contrario, si V es C<V1...Vk> , la inferencia depende del parámetro de
tipo i-ésima de C :
Si es covariante, se realiza una inferencia de límite inferior .
Si es contravariante, se realiza una inferencia enlazada en el límite superior .
Si es invariable, se realiza una inferencia exacta .
De lo contrario, no se realiza ninguna inferencia.
Si se aplica cualquiera de estos casos, se realiza una inferencia desde cada Ui hasta
el correspondiente, como se indica a continuación Vi :
Si Ui no se sabe que es un tipo de referencia, se realiza una inferencia exacta .
De lo contrario, si V es un tipo de matriz, se realiza una inferencia de límite
superior .
De lo contrario, si U es C<U1...Uk> , la inferencia depende del parámetro de
tipo i-ésima de C :
Si es covariante, se realiza una inferencia enlazada en el límite superior .
Si es contravariante, se realiza una inferencia de límite inferior .
Si es invariable, se realiza una inferencia exacta .
candidatos. Para cada límite inferior U de Xi todos los tipos Uj a los que no se
haya quitado una conversión implícita del U conjunto de candidatos. Para cada
límite superior U de Xi todos los tipos Uj a partir de los cuales no se quita una
conversión implícita de en U el conjunto de candidatos.
Si entre los tipos de candidatos restantes Uj hay un tipo único V desde el que hay
una conversión implícita a todos los demás tipos candidatos, Xi se fija en V .
De lo contrario, se produce un error en la inferencia de tipos.
Como ejemplo de inferencia de tipos que implica funciones anónimas, tenga en cuenta
el Select método de extensión declarado en la System.Linq.Enumerable clase:
C#
namespace System.Linq
Func<TSource,TResult> selector)
Suponiendo que el System.Linq espacio de nombres se importó con una using cláusula
y, dada una clase Customer con una Name propiedad de tipo string , el Select método
se puede usar para seleccionar los nombres de una lista de clientes:
C#
C#
C#
C#
return f2(f1(value));
C#
continúa del modo siguiente: en primer lugar, el argumento "1:15:30" está relacionado
con el value parámetro, X que se infiere en string . A continuación, el parámetro de la
primera función anónima, s , recibe el tipo deducido string y la expresión
TimeSpan.Parse(s) está relacionada con el tipo de valor devuelto de f1 , infiriendo Y a
C#
M<S1...Sn>
A diferencia del algoritmo de inferencia de tipos para las llamadas de método genérico,
en este caso solo hay tipos de argumento, no hay expresiones de argumento. En
concreto, no hay ninguna función anónima y, por lo tanto, no es necesario que haya
varias fases de inferencia.
C#
Resolución de sobrecarga
La resolución de sobrecarga es un mecanismo en tiempo de enlace para seleccionar el
mejor miembro de función que se va a invocar a partir de una lista de argumentos y un
conjunto de miembros de función candidatos. La resolución de sobrecarga selecciona el
miembro de función que se va a invocar en los siguientes contextos distintos dentro de
C#:
Las listas de parámetros para cada uno de los miembros de la función candidata se
construyen de la siguiente manera:
mejor que Mq si
En caso de que las secuencias de tipo de parámetro {P1, P2, ..., Pn} y {Q1, Q2, ...,
Qn} sean equivalentes (es decir Pi , cada una tiene una conversión de identidad en la
correspondiente Qi ), se aplican las siguientes reglas de separación de desempates, en
orden, para determinar el mejor miembro de función.
C#
class G1<U>
class G2<U,V>
{
paso más.
Si E no está clasificado como una variable, se crea una variable local temporal
de E tipo y el valor de E se asigna a esa variable. E a continuación, se
reclasifica como una referencia a esa variable local temporal. La variable
temporal es accesible como this en M , pero no de otra manera. Por lo tanto,
solo cuando E es una variable verdadera, el autor de la llamada puede observar
los cambios que M realiza en this .
La lista de argumentos se evalúa como se describe en listas de argumentos.
Se invoca a M . La variable a la que hace referencia E se convierte en la variable
a la que hace referencia this .
paso más.
La lista de argumentos se evalúa como se describe en listas de argumentos.
Si el tipo de E es un value_type, se realiza una conversión boxing (conversiones
boxing) para convertir E al tipo object y E se considera que es de tipo object
en los pasos siguientes. En este caso, M solo puede ser un miembro de
System.Object .
E Se comprueba que el valor de es válido. Si el valor de E es null ,
En estas situaciones, se considera que la instancia con conversión boxing contiene una
variable del value_type y esta variable se convierte en la variable a la que hace referencia
this la invocación de miembros de función. En concreto, esto significa que cuando se
Expresiones primarias
Las expresiones primarias incluyen las formas más sencillas de las expresiones.
antlr
primary_expression
: primary_no_array_creation_expression
| array_creation_expression
primary_no_array_creation_expression
: literal
| interpolated_string_expression
| simple_name
| parenthesized_expression
| member_access
| invocation_expression
| element_access
| this_access
| base_access
| post_increment_expression
| post_decrement_expression
| object_creation_expression
| delegate_creation_expression
| anonymous_object_creation_expression
| typeof_expression
| checked_expression
| unchecked_expression
| default_value_expression
| nameof_expression
| anonymous_method_expression
| primary_no_array_creation_expression_unsafe
C#
C#
Literales
Un primary_expression que consta de un literal (literales) se clasifica como un valor.
Cadenas interpoladas
Un interpolated_string_expression consta de un $ signo seguido de un literal de cadena
normal o textual, en el que los huecos, delimitados por { y } , escriben expresiones y
especificaciones de formato. Una expresión de cadena interpolada es el resultado de
una interpolated_string_literal que se ha dividido en tokens individuales, tal y como se
describe en literales de cadena interpolados.
antlr
interpolated_string_expression
: '$' interpolated_regular_string
| '$' interpolated_verbatim_string
interpolated_regular_string
: interpolated_regular_string_whole
| interpolated_regular_string_start interpolated_regular_string_body
interpolated_regular_string_end
interpolated_regular_string_body
interpolation
: expression
interpolated_verbatim_string
: interpolated_verbatim_string_whole
| interpolated_verbatim_string_start interpolated_verbatim_string_body
interpolated_verbatim_string_end
interpolated_verbatim_string_body
: interpolation (interpolated_verbatim_string_mid interpolation)+
Si un interpolated_regular_string_whole o un interpolated_verbatim_string_whole
siguen el $ signo, el literal de cadena de formato es ese token.
De lo contrario, el literal de cadena de formato consta de:
En primer lugar, interpolated_regular_string_start o
interpolated_verbatim_string_start
A continuación, para cada número I de 0 a N-1 :
La representación decimal de I
Después, si la interpolación correspondiente tiene una constant_expression,
una , (coma) seguida de la representación decimal del valor de la
constant_expression
A continuación, el interpolated_regular_string_mid,
interpolated_regular_string_end, interpolated_verbatim_string_mid o
interpolated_verbatim_string_end inmediatamente después de la
interpolación correspondiente.
Los argumentos subsiguientes son simplemente las expresiones de las interpolaciones (si
existen), en orden.
TODO: ejemplos.
Nombres simples
Un simple_name consta de un identificador, seguido opcionalmente de una lista de
argumentos de tipo:
antlr
simple_name
: identifier type_argument_list?
antlr
parenthesized_expression
Acceso a miembros
Un member_access consta de un primary_expression, un predefined_type o un
qualified_alias_member, seguido de un token " . ", seguido de un identificador, seguido
opcionalmente de un type_argument_list.
antlr
member_access
predefined_type
Una member_access tiene el formato E.I o el formato E.I<A1, ..., Ak> , donde E es
una expresión principal, I es un identificador único y <A1, ..., Ak> es un
type_argument_list opcional. Si no se especifica ningún type_argument_list , considere la
posibilidad K de que sea cero.
C#
struct Color
class A
void F() {
Ambigüedades de la gramática
Las producciones de simple_name (nombres simples) y member_access (acceso a
miembros) pueden dar lugar a ambigüedades en la gramática de expresiones. Por
ejemplo, la instrucción:
C#
F(G<A,B>(7));
podría interpretarse como una llamada a F con dos argumentos, G < A y B > (7) .
Como alternativa, podría interpretarse como una llamada a F con un argumento, que es
una llamada a un método genérico G con dos argumentos de tipo y un argumento
normal.
C#
( ) ] } : ; , . ? == != | ^
C#
F(G<A,B>(7));
, según esta regla, se interpretará como una llamada a F con un argumento, que es una
llamada a un método genérico G con dos argumentos de tipo y un argumento normal.
Las instrucciones
C#
cada uno de ellos se interpretará como una llamada a F con dos argumentos. La
instrucción
C#
se interpretará como un operador menor que, mayor que y unario más, como si se
hubiera escrito la instrucción x = (F < A) > (+y) , en lugar de un simple_name con un
type_argument_list seguido de un operador binario Plus. En la instrucción
C#
x = y is C<T> + z;
Expresiones de invocación
Un invocation_expression se utiliza para invocar un método.
antlr
invocation_expression
Invocaciones de método
conjunto. (Esta última regla solo tiene efecto cuando el grupo de métodos era el
resultado de una búsqueda de miembros en un parámetro de tipo que tiene una
clase base efectiva que no es un objeto y un conjunto de interfaces efectivo no
vacío).
Si el conjunto resultante de métodos candidatos está vacío, se abandona el
procesamiento posterior a lo largo de los pasos siguientes y, en su lugar, se intenta
procesar la invocación como una invocación de método de extensión
(invocaciones de método de extensión). Si se produce un error, no existe ningún
método aplicable y se produce un error en tiempo de enlace.
El mejor método del conjunto de métodos candidatos se identifica mediante las
reglas de resolución de sobrecarga de la resolución de sobrecarga. Si no se puede
identificar un único método mejor, la invocación del método es ambigua y se
produce un error en tiempo de enlace. Al realizar la resolución de sobrecarga, los
parámetros de un método genérico se tienen en cuenta después de sustituir los
argumentos de tipo (proporcionados o deducidos) para los parámetros de tipo de
método correspondientes.
Se realiza la validación final del mejor método elegido:
El método se valida en el contexto del grupo de métodos: Si el mejor método es
un método estático, el grupo de métodos debe haber sido el resultado de un
simple_name o de un member_access a través de un tipo. Si el mejor método es
un método de instancia, el grupo de métodos debe haber sido el resultado de
un simple_name, un member_access a través de una variable o un valor, o un
base_access. Si ninguno de estos requisitos es true, se produce un error en
tiempo de enlace.
Si el mejor método es un método genérico, los argumentos de tipo
(suministrados o deducidos) se comprueban con las restricciones (quecumplen
las restricciones) declaradas en el método genérico. Si algún argumento de tipo
no satisface las restricciones correspondientes en el parámetro de tipo, se
produce un error en tiempo de enlace.
Una vez que se ha seleccionado y validado un método en tiempo de enlace según los
pasos anteriores, la invocación real en tiempo de ejecución se procesa de acuerdo con
las reglas de invocación de miembros de función descritas en la comprobación en
tiempo de compilación de la resolución dinámica de sobrecarga.
C#
expr . identifier ( )
C#
C . identifier ( expr )
El nombre de Mj es Identifier
Mj es accesible y aplicable cuando se aplica a los argumentos como un método
Las reglas anteriores implican que los métodos de instancia tienen prioridad sobre los
métodos de extensión, que los métodos de extensión disponibles en las declaraciones
de espacios de nombres internos tienen prioridad sobre los métodos de extensión
disponibles en las declaraciones de espacios de nombres exteriores y que los métodos
de extensión declarados directamente en un espacio de nombres tienen prioridad sobre
los métodos de extensión importados en el mismo espacio de nombres con una
directiva Por ejemplo:
C#
class A { }
class B
class C
class X
b.F(1); // B.F(int)
c.F(1); // C.F(object)
c.F("hello"); // C.F(object)
C#
namespace N1
namespace N2
using N1;
class Test
1.F();
2.G();
3.H();
E.F(1)
D.G(2)
C.H(3)
D.G tiene prioridad sobre y C.G E.F tiene prioridad sobre D.F y C.F .
Invocaciones de delegado
más.
D Se comprueba que el valor de es válido. Si el valor de D es null ,
System.NullReferenceException se produce una excepción y no se ejecuta ningún
paso más.
De lo contrario, D es una referencia a una instancia de delegado. Las invocaciones
de miembros de función (comprobación en tiempo de compilación de la
resolución dinámica de sobrecarga) se realizan en cada una de las entidades a las
que se puede llamar en la lista de invocaciones del delegado. Para las entidades a
las que se puede llamar que se componen de un método de instancia y de
instancia, la instancia de para la invocación es la instancia contenida en la entidad
a la que se puede
Acceso a elementos
Un element_access consta de un primary_no_array_creation_expression, seguido de un [
token "", seguido de un argument_list, seguido de un ] token "". El argument_list se
compone de uno o más argumentos, separados por comas.
antlr
element_access
Acceso a matriz
tipos.
más.
Las expresiones de índice del argument_list se evalúan en orden, de izquierda a
derecha. Después de la evaluación de cada expresión de índice, se realiza una
conversión implícita (conversiones implícitas) en uno de los siguientes tipos: int ,
uint , long , ulong . Se elige el primer tipo de esta lista para el que existe una
Acceso a indizador
En el caso de un acceso de indexador, el primary_no_array_creation_expression de la
element_access debe ser una variable o valor de un tipo de clase, struct o interfaz, y este
tipo debe implementar uno o más indexadores aplicables con respecto a la
argument_list del element_access.
Este acceso
Un this_access consta de la palabra reservada this .
antlr
this_access
: 'this'
debe estar asignada definitivamente en todas las rutas de acceso de ejecución del
constructor de instancia.
Cuando this se utiliza en una primary_expression dentro de un método de
instancia o un descriptor de acceso de instancia de un struct, se clasifica como una
variable. El tipo de la variable es el tipo de instancia (el tipo de instancia) del struct
en el que se produce el uso.
Si el método o el descriptor de acceso no es un iterador (iteradores), la this
variable representa la estructura para la que se invocó el método o descriptor
de acceso, y se comporta exactamente igual que un ref parámetro del tipo de
estructura.
Si el método o descriptor de acceso es un iterador, la this variable representa
una copia del struct para el que se invocó el método o descriptor de acceso, y
se comporta exactamente igual que un parámetro de valor del tipo de
estructura.
Acceso base
Un base_access consta de la palabra reservada base seguida de un . token "" y un
identificador o un argument_list entre corchetes:
antlr
base_access
Un base_access se utiliza para tener acceso a los miembros de clase base que están
ocultos por miembros con el mismo nombre en la clase o el struct actual. Solo se
permite un base_access en el bloque de un constructor de instancia, un método de
instancia o un descriptor de acceso de instancia. Cuando base.I se produce en una
clase o struct, I debe indicar un miembro de la clase base de esa clase o estructura. Del
mismo modo, cuando base[E] se produce en una clase, debe existir un indexador
aplicable en la clase base.
base.I y base[E] corresponden a this.I y this[E] , salvo this que se ve como una
post_increment_expression
: primary_expression '++'
post_decrement_expression
: primary_expression '--'
operación.
Operador new
El new operador se usa para crear nuevas instancias de tipos.
antlr
object_creation_expression
object_or_collection_initializer
: object_initializer
| collection_initializer
Si T es un class_type:
Se asigna una nueva instancia de la clase T . Si no hay suficiente memoria
disponible para asignar la nueva instancia, System.OutOfMemoryException se
produce una excepción y no se ejecuta ningún paso más.
Todos los campos de la nueva instancia se inicializan con sus valores
predeterminados (valores predeterminados).
El constructor de instancia se invoca de acuerdo con las reglas de invocación de
miembros de función (comprobación en tiempo de compilación de la resolución
dinámica de sobrecarga). Se pasa automáticamente una referencia a la instancia
recién asignada al constructor de instancia y se puede tener acceso a la
instancia desde dentro de ese constructor como this .
Si T es un struct_type:
Una instancia de tipo T se crea asignando una variable local temporal. Puesto
que se requiere un constructor de instancia de un struct_type para asignar
definitivamente un valor a cada campo de la instancia que se va a crear, no es
necesario inicializar la variable temporal.
El constructor de instancia se invoca de acuerdo con las reglas de invocación de
miembros de función (comprobación en tiempo de compilación de la resolución
dinámica de sobrecarga). Se pasa automáticamente una referencia a la instancia
recién asignada al constructor de instancia y se puede tener acceso a la
instancia desde dentro de ese constructor como this .
Inicializadores de objeto
Un inicializador de objeto especifica valores para cero o más campos, propiedades o
elementos indizados de un objeto.
antlr
object_initializer
member_initializer_list
member_initializer
initializer_target
: identifier
initializer_value
: expression
| object_or_collection_initializer
Inicializador de miembro que especifica una expresión después de que el signo igual se
procese de la misma manera que una asignación (asignación simple) al destino.
C#
int x, y;
C#
__a.X = 0;
__a.Y = 1;
Point a = __a;
donde __a es una variable temporal invisible e inaccesible en caso contrario. La clase
siguiente representa un rectángulo creado a partir de dos puntos:
C#
C#
P1 = new Point { X = 0, Y = 1 },
P2 = new Point { X = 2, Y = 3 }
};
C#
__p1.X = 0;
__p1.Y = 1;
__r.P1 = __p1;
__p2.X = 2;
__p2.Y = 3;
__r.P2 = __p2;
Rectangle r = __r;
donde __r __p1 y __p2 son variables temporales que, de lo contrario, son invisibles e
inaccesibles.
C#
C#
P1 = { X = 0, Y = 1 },
P2 = { X = 2, Y = 3 }
};
C#
__r.P1.X = 0;
__r.P1.Y = 1;
__r.P2.X = 2;
__r.P2.Y = 3;
Rectangle r = __r;
C#
var c = new C {
x = true,
y = { a = "Hello" },
z = { 1, 2, 3 },
["x"] = 5,
[1,2] = {}
};
C#
__c.x = true;
__c.y.a = "Hello";
__c.z.Add(1);
__c.z.Add(2);
__c.z.Add(3);
__c[__i1] = 5;
__c[__i2,__i3].Add("a");
__c[__i2,__i3].Add("b");
var c = __c;
donde __c , etc., se generan variables que son invisibles e inaccesibles para el código
fuente. Tenga en cuenta que los argumentos de [0,0] se evalúan solo una vez, y los
argumentos de [1,2] se evalúan una vez, aunque nunca se utilicen.
Inicializadores de colección
antlr
collection_initializer
element_initializer_list
element_initializer
: non_assignment_expression
expression_list
C#
C#
string name;
new Contact {
},
new Contact {
PhoneNumbers = { "650-555-0199" }
};
C#
__c1.PhoneNumbers.Add("206-555-0101");
__c1.PhoneNumbers.Add("425-882-8080");
__clist.Add(__c1);
__c2.PhoneNumbers.Add("650-555-0199");
__clist.Add(__c2);
donde __clist __c1 y __c2 son variables temporales que, de lo contrario, son invisibles
e inaccesibles.
antlr
array_creation_expression
Una expresión de creación de matriz del primer formulario asigna una instancia de
matriz del tipo resultante de la eliminación de cada una de las expresiones individuales
de la lista de expresiones. Por ejemplo, la expresión de creación de matriz new
int[10,20] genera una instancia de matriz de tipo int[,] y la expresión new int[10][,]
de creación de matriz genera una matriz de tipo int[][,] . Cada expresión de la lista de
expresiones debe ser de tipo int , uint , long o ulong , o bien se puede convertir
implícitamente a uno o varios de estos tipos. El valor de cada expresión determina la
longitud de la dimensión correspondiente en la instancia de matriz recién asignada.
Dado que la longitud de una dimensión de matriz no debe ser negativa, es un error en
tiempo de compilación tener un constant_expression con un valor negativo en la lista de
expresiones.
En una expresión de creación de matriz del segundo o tercer formulario, el rango del
tipo de matriz o especificador de rango especificado debe coincidir con el del
inicializador de matriz. Las longitudes de las dimensiones individuales se deducen del
número de elementos de cada uno de los niveles de anidamiento correspondientes del
inicializador de matriz. Por lo tanto, la expresión
C#
corresponde exactamente a
C#
Se hace referencia a una expresión de creación de matriz del tercer formulario como
una expresión de creación de matriz con tipo * implícita _. Es similar a la segunda
forma, salvo que el tipo de elemento de la matriz no se proporciona explícitamente,
pero se determina como el mejor tipo común (encontrar el mejor tipo común de un
conjunto de expresiones) del conjunto de expresiones en el inicializador de matriz. En el
caso de una matriz multidimensional, es decir, una en la que el _rank_specifier *
contiene al menos una coma, este conjunto incluye todas las expresiones que se
encuentran en los array_initializer anidados.
Los inicializadores de matriz se describen con más detalle en inicializadores de matriz.
paso.
Se asigna una instancia de matriz con las longitudes de dimensión dadas. Si no hay
suficiente memoria disponible para asignar la nueva instancia,
System.OutOfMemoryException se produce una excepción y no se ejecuta ningún
paso más.
Todos los elementos de la nueva instancia de matriz se inicializan con sus valores
predeterminados (valores predeterminados).
Si la expresión de creación de matriz contiene un inicializador de matriz, cada
expresión del inicializador de matriz se evalúa y se asigna a su elemento de matriz
correspondiente. Las evaluaciones y las asignaciones se realizan en el orden en el
que se escriben las expresiones en el inicializador de matriz; es decir, los elementos
se inicializan en el orden de índice creciente, con la dimensión situada más a la
derecha aumentando primero. Si la evaluación de una expresión determinada o la
asignación subsiguiente al elemento de matriz correspondiente produce una
excepción, no se inicializa ningún otro elemento (y los demás elementos tendrán
sus valores predeterminados).
Una expresión de creación de matriz permite la creación de instancias de una matriz con
elementos de un tipo de matriz, pero los elementos de dicha matriz se deben inicializar
manualmente. Por ejemplo, la instrucción
C#
crea una matriz unidimensional con 100 elementos de tipo int[] . El valor inicial de
cada elemento es null . No es posible que la misma expresión de creación de matriz
cree también instancias de las submatrices y la instrucción
C#
C#
Cuando una matriz de matrices tiene una forma "rectangular", es decir, cuando las
submatrices tienen la misma longitud, es más eficaz usar una matriz multidimensional.
En el ejemplo anterior, la creación de instancias de la matriz de matrices crea 101
objetos, una matriz externa y submatrices de 100. En cambio,
C#
crea un solo objeto, una matriz bidimensional y realiza la asignación en una única
instrucción.
C#
Las expresiones de creación de matrices con tipo implícito se pueden combinar con
inicializadores de objeto anónimos (expresiones de creación de objetos anónimos) para
crear estructuras de datos con tipos anónimos. Por ejemplo:
C#
new {
},
new {
};
antlr
delegate_creation_expression
paso más.
Si el valor de E es null , System.NullReferenceException se produce una
excepción y no se ejecuta ningún paso más.
Se asigna una nueva instancia del tipo de delegado D . Si no hay suficiente
memoria disponible para asignar la nueva instancia,
System.OutOfMemoryException se produce una excepción y no se ejecuta ningún
paso más.
La nueva instancia de delegado se inicializa con la misma lista de invocación
que la instancia de delegado proporcionada por E .
C#
class A
return x * x;
el A.f campo se inicializa con un delegado que hace referencia al segundo Square
método porque ese método coincide exactamente con la lista de parámetros formales y
el tipo de valor devuelto de DoubleFunc . Si el segundo Square método no estuviera
presente, se habría producido un error en tiempo de compilación.
antlr
anonymous_object_creation_expression
: 'new' anonymous_object_initializer
anonymous_object_initializer
member_declarator_list
member_declarator
: simple_name
| member_access
| base_access
| null_conditional_member_access
lectura que se deducen del inicializador de objeto anónimo utilizado para crear una
instancia del tipo. En concreto, un inicializador de objeto anónimo con el formato
C#
C#
class __Anonymous1
...
f1 = a1;
f2 = a2;
...
fn = an;
...
En el ejemplo
C#
p1 = p2;
la asignación en la última línea se permite porque p1 y p2 son del mismo tipo anónimo.
Los Equals GetHashcode métodos y en los tipos anónimos invalidan los métodos
heredados de y object se definen en términos de Equals y de las GetHashcode
propiedades, de modo que dos instancias del mismo tipo anónimo son iguales si y solo
si todas sus propiedades son iguales.
C#
identifier
expr.identifier
C#
identifier = identifier
identifier = expr.identifier
El operador typeof
El typeof operador se usa para obtener el System.Type objeto para un tipo.
antlr
typeof_expression
unbound_type_name
: identifier generic_dimension_specifier?
generic_dimension_specifier
comma
: ','
En el ejemplo
C#
using System;
class X<T>
Type[] t = {
typeof(int),
typeof(System.Int32),
typeof(string),
typeof(double[]),
typeof(void),
typeof(T),
typeof(X<T>),
typeof(X<X<T>>),
typeof(X<>)
};
Console.WriteLine(t[i]);
class Test
X<int>.PrintTypes();
Consola
System.Int32
System.Int32
System.String
System.Double[]
System.Void
System.Int32
X`1[System.Int32]
X`1[X`1[System.Int32]]
X`1[T]
antlr
checked_expression
unchecked_expression
de un tipo entero.
Los + operadores binarios predefinidos,, - * y / (operadores aritméticos),
cuando ambos operandos son de tipos enteros.
Conversiones numéricas explícitas (Conversiones numéricas explícitas) de un tipo
entero a otro tipo entero, o de float o double a un tipo entero.
En el ejemplo
C#
class Test
En el ejemplo
C#
class Test
C#
class Test
return x * y;
C#
class Test
Las dos constantes hexadecimales anteriores son de tipo uint . Dado que las constantes
están fuera del int intervalo, sin el unchecked operador, las conversiones a int
generarán errores en tiempo de compilación.
antlr
default_value_expression
Expresiones Name
Un nameof_expression se utiliza para obtener el nombre de una entidad de programa
como una cadena de constante.
antlr
nameof_expression
named_entity
: simple_name
named_entity_target
: 'this'
| 'base'
| named_entity
| predefined_type
| qualified_alias_member
TODO: ejemplos
Operadores unarios
Los ? + operadores,, - , ! , ~ , ++ , -- , CAST y await se denominan operadores
unarios.
antlr
unary_expression
: primary_expression
| null_conditional_expression
| '+' unary_expression
| '-' unary_expression
| '!' unary_expression
| '~' unary_expression
| pre_increment_expression
| pre_decrement_expression
| cast_expression
| await_expression
| unary_expression_unsafe
antlr
null_conditional_expression
: primary_expression null_conditional_operations
null_conditional_operations
C#
C#
C#
C#
a.b?[0]?.c();
su significado es equivalente a:
C#
lo que es equivalente a:
C#
C#
var x = a.b?[0]?.c();
antlr
null_conditional_member_access
antlr
null_conditional_invocation_expression
C#
Para cada uno de estos operadores, el resultado es simplemente el valor del operando.
Negación de entero:
C#
C#
Negación decimal:
C#
C#
C#
C#
pre_increment_expression
: '++' unary_expression
pre_decrement_expression
: '--' unary_expression
Expresiones de conversión
Un cast_expression se utiliza para convertir explícitamente una expresión a un tipo
determinado.
antlr
cast_expression
Expresiones Await
El operador Await se usa para suspender la evaluación de la función asincrónica
envolvente hasta que se haya completado la operación asincrónica representada por el
operando.
antlr
await_expression
: 'await' unary_expression
Dentro de una función asincrónica, await no se puede usar como identificador. Por lo
tanto, no hay ambigüedades sintácticas entre las expresiones Await y varias expresiones
que intervienen en identificadores. Fuera de las funciones asincrónicas, await actúa
como un identificador normal.
El propósito del GetResult método es obtener el resultado de la tarea una vez que ha
finalizado. Este resultado puede ser una finalización correcta, posiblemente con un valor
de resultado, o puede ser una excepción producida por el GetResult método.
Operadores aritméticos
Los * / operadores,, % , + y - se denominan operadores aritméticos.
antlr
multiplicative_expression
: unary_expression
additive_expression
: multiplicative_expression
Operador de multiplicación
En el caso de una operación x * y con el formato, se aplica la resolución de
sobrecargas del operador binario (resolución de sobrecarga del operador binario) para
seleccionar una implementación de operador específica. Los operandos se convierten en
los tipos de parámetro del operador seleccionado y el tipo del resultado es el tipo de
valor devuelto del operador.
Multiplicación de enteros:
C#
C#
Multiplicación decimal:
C#
Operador de división
En el caso de una operación x / y con el formato, se aplica la resolución de
sobrecargas del operador binario (resolución de sobrecarga del operador binario) para
seleccionar una implementación de operador específica. Los operandos se convierten en
los tipos de parámetro del operador seleccionado y el tipo del resultado es el tipo de
valor devuelto del operador.
División de enteros:
C#
La división redondea el resultado hacia cero. Por lo tanto, el valor absoluto del
resultado es el entero posible más grande que sea menor o igual que el valor
absoluto del cociente de los dos operandos. El resultado es cero o positivo cuando
los dos operandos tienen el mismo signo y cero o negativo cuando los dos
operandos tienen signos opuestos.
C#
División decimal:
C#
Operador de resto
En el caso de una operación x % y con el formato, se aplica la resolución de
sobrecargas del operador binario (resolución de sobrecarga del operador binario) para
seleccionar una implementación de operador específica. Los operandos se convierten en
los tipos de parámetro del operador seleccionado y el tipo del resultado es el tipo de
valor devuelto del operador.
A continuación se enumeran los operadores de resto predefinidos. Todos los
operadores calculan el resto de la división entre x y y .
Resto entero:
C#
C#
Resto decimal:
C#
Operador de suma
En el caso de una operación x + y con el formato, se aplica la resolución de
sobrecargas del operador binario (resolución de sobrecarga del operador binario) para
seleccionar una implementación de operador específica. Los operandos se convierten en
los tipos de parámetro del operador seleccionado y el tipo del resultado es el tipo de
valor devuelto del operador.
Suma de enteros:
C#
En un checked contexto, si la suma está fuera del intervalo del tipo de resultado,
System.OverflowException se produce una excepción. En un unchecked contexto,
no se informan los desbordamientos y se descartan los bits significativos de orden
superior fuera del intervalo del tipo de resultado.
C#
La suma se calcula de acuerdo con las reglas de aritmética de IEEE 754. En la tabla
siguiente se enumeran los resultados de todas las posibles combinaciones de
valores finitos distintos de cero, ceros, infinitos y NaN. En la tabla, x e y son
valores finitos distintos de cero y z es el resultado de x + y . Si x e y tienen la
misma magnitud pero signos opuestos, z es cero positivo. Si x + y es demasiado
grande para representarlo en el tipo de destino, z es un infinito con el mismo
signo que x + y .
Suma decimal:
C#
C#
Concatenación de cadenas:
C#
C#
using System;
class Test
string s = null;
int i = 1;
float f = 1.2300E+15F;
decimal d = 2.900m;
C#
Operador de resta
En el caso de una operación x - y con el formato, se aplica la resolución de
sobrecargas del operador binario (resolución de sobrecarga del operador binario) para
seleccionar una implementación de operador específica. Los operandos se convierten en
los tipos de parámetro del operador seleccionado y el tipo del resultado es el tipo de
valor devuelto del operador.
Resta de enteros:
C#
int operator -(int x, int y);
C#
Resta decimal:
C#
C#
C#
C#
C#
class C
class Test
Operadores de desplazamiento
Los << >> operadores y se utilizan para realizar operaciones de desplazamiento de bits.
antlr
shift_expression
: additive_expression
Para una operación con el formato x << count o x >> count , se aplica la resolución de
sobrecargas del operador binario (resolución de sobrecarga del operador binario) para
seleccionar una implementación de operador específica. Los operandos se convierten en
los tipos de parámetro del operador seleccionado y el tipo del resultado es el tipo de
valor devuelto del operador.
Desplazar a la izquierda:
C#
Los bits de orden superior fuera del intervalo del tipo de resultado de x se
descartan, los bits restantes se desplazan a la izquierda y las posiciones de bits
vacías de orden inferior se establecen en cero.
Desplazamiento a la derecha:
C#
int operator >>(int x, int count);
Cuando x es de tipo int o long , los bits de orden inferior de x se descartan, los
bits restantes se desplazan a la derecha y las posiciones de bits vacías de orden
superior se establecen en cero si x no es negativo y se establecen en uno si x es
negativo.
Cuando el tipo de x es int o uint , el número de turnos viene dado por los cinco
bits de orden inferior de count . En otras palabras, el número de turnos se calcula
a partir de count & 0x1F .
Cuando el tipo de x es long o ulong , el recuento de desplazamiento lo
proporcionan los seis bits de orden inferior de count . En otras palabras, el número
de turnos se calcula a partir de count & 0x3F .
Cuando el operando izquierdo del >> operador es de un tipo entero con signo, el
operador realiza un desplazamiento aritmético a la derecha, donde el valor del bit más
significativo (el bit de signo) del operando se propaga a las posiciones de bits vacías de
orden superior. Cuando el operando izquierdo del >> operador es de un tipo entero sin
signo, el operador realiza un desplazamiento lógico derecho en el que las posiciones de
bits vacías de orden superior siempre se establecen en cero. Para realizar la operación
opuesta a la que se infiere del tipo de operando, se pueden usar conversiones explícitas.
Por ejemplo, si x es una variable de tipo int , la operación unchecked((int)((uint)x >>
y)) realiza un desplazamiento lógico a la derecha de x .
Operadores de comprobación de tipos y
relacionales
Los == != operadores,, < ,, > <= , >= is y as se denominan operadores relacionales y
de prueba de tipos.
antlr
relational_expression
: shift_expression
equality_expression
: relational_expression
C#
Cada uno de estos operadores compara los valores numéricos de los dos operandos de
tipo entero y devuelve un bool valor que indica si la relación concreta es true o false .
C#
Los operadores comparan los operandos según las reglas del estándar IEEE 754:
Cuando ninguno de los operandos es NaN, los operadores comparan los valores
de los dos operandos de punto flotante con respecto a la ordenación.
C#
-inf < -max < ... < -min < -0.0 == +0.0 < +min < ... < +max < +inf
donde min y max son los valores finitos positivos más pequeños y mayores que se
pueden representar en el formato de punto flotante dado. Los efectos importantes
de este orden son:
Los ceros negativos y positivos se consideran iguales.
Un infinito negativo se considera menor que todos los demás valores, pero es
igual a otro infinito negativo.
Un infinito positivo se considera mayor que el resto de los valores, pero es igual
a otro infinito positivo.
C#
Cada uno de estos operadores compara los valores numéricos de los dos operandos
decimales y devuelve un bool valor que indica si la relación concreta es true o false .
Cada comparación decimal es equivalente a usar el operador relacional o de igualdad
correspondiente de tipo System.Decimal .
C#
C#
C#
referencia explícitas) desde el tipo de uno de los operandos al tipo del otro
operando.
Un operando es un valor de T tipo T , donde es un type_parameter y el otro
operando es el literal null . Además, no T tiene la restricción de tipo de valor.
C#
class C<T>
void F(T x) {
...
C#
using System;
class Test
string s = "Test";
string t = string.Copy(s);
Console.WriteLine(s == t);
Console.WriteLine((object)s == t);
Console.WriteLine(s == (object)t);
Console.WriteLine((object)s == (object)t);
genera el resultado
Consola
True
False
False
False
Las s t variables y hacen referencia a dos string instancias distintas que contienen los
mismos caracteres. La primera comparación genera resultados True porque el operador
de igualdad de cadena predefinido (operadores de igualdad de cadena) está
seleccionado cuando ambos operandos son de tipo string . El resto de las
comparaciones False se generan porque el operador de igualdad de tipos de referencia
predefinido se selecciona cuando uno o ambos operandos son del tipo object .
Tenga en cuenta que la técnica anterior no es significativa para los tipos de valor. En el
ejemplo
C#
class Test
int i = 123;
int j = 123;
System.Console.WriteLine((object)i == (object)j);
C#
Dos string valores se consideran iguales cuando se cumple una de las siguientes
condiciones:
C#
C#
x == null
null == x
x != null
null != x
El operador is
El is operador se usa para comprobar dinámicamente si el tipo en tiempo de ejecución
de un objeto es compatible con un tipo determinado. El resultado de la operación E is
T , donde E es una expresión y T es un tipo, es un valor booleano que indica si se E
Tenga en cuenta que las conversiones definidas por el usuario no se tienen en cuenta
por el is operador.
El operador as
El as operador se usa para convertir explícitamente un valor en un tipo de referencia
determinado o un tipo que acepta valores NULL. A diferencia de una expresión de
conversión (expresiones de conversión), el as operador nunca produce una excepción.
En su lugar, si la conversión indicada no es posible, el valor resultante es null .
En una operación del formulario E as T , E debe ser una expresión y T debe ser un
tipo de referencia, un parámetro de tipo conocido como un tipo de referencia o un tipo
que acepta valores NULL. Además, al menos uno de los siguientes debe ser true o, de lo
contrario, se producirá un error en tiempo de compilación:
C#
E is T ? (T)(E) : (T)null
salvo que E solo se evalúa una vez. Se puede esperar que el compilador optimice E as
T para realizar como máximo una comprobación de tipos dinámicos en lugar de las dos
C#
E is T ? (T)(object)(E) : (T)null
Tenga en cuenta que algunas conversiones, como las conversiones definidas por el
usuario, no son posibles con el as operador y deben realizarse en su lugar mediante
expresiones de conversión.
En el ejemplo
C#
class X
public U H<U>(object o) {
Operadores lógicos
Los & ^ operadores, y | se denominan operadores lógicos.
antlr
and_expression
: equality_expression
exclusive_or_expression
: and_expression
inclusive_or_expression
: exclusive_or_expression
C#
El & operador calcula el lógico bit AND a bit de los dos operandos, el | operador calcula
el operador lógico bit OR a bit de los dos operandos y el ^ operador calcula la lógica
exclusiva bit OR a bit de los dos operandos. No es posible realizar desbordamientos en
estas operaciones.
C#
E operator &(E x, E y);
C#
Expresiones booleanas en SQL. Para asegurarse de que los resultados generados por los
& | operadores y para los bool? operandos son coherentes con la lógica de tres
valores de SQL, se proporcionan los siguientes operadores predefinidos:
C#
En la tabla siguiente se enumeran los resultados generados por estos operadores para
todas las combinaciones de los valores true , false y null .
x y x & y x | y
antlr
conditional_and_expression
: inclusive_or_expression
conditional_or_expression
: conditional_and_expression
Para obtener un ejemplo de un tipo que implementa operator true y operator false ,
vea Database Boolean Type.
antlr
null_coalescing_expression
: conditional_or_expression
Una expresión de fusión nula del formulario a ?? b requiere a que sea de un tipo que
acepte valores NULL o un tipo de referencia. Si a no es null, el resultado de a ?? b es a
; de lo contrario, el resultado es b . La operación b solo se evalúa si a es NULL.
Operador condicional
El ?: operador se denomina operador condicional. En ocasiones también se denomina
operador ternario.
antlr
conditional_expression
: null_coalescing_expression
El primer operando del ?: operador debe ser una expresión que se pueda convertir
implícitamente a bool , o una expresión de un tipo que implemente operator true . Si
no se cumple ninguno de estos requisitos, se produce un error en tiempo de
compilación.
Por motivos históricos, hay dos tipos sintácticos de funciones anónimas, es decir,
lambda_expression s y anonymous_method_expression s. Para casi todos los propósitos,
lambda_expression s son más concisos y expresivos que anonymous_method_expression
s, que permanecen en el lenguaje por compatibilidad con versiones anteriores.
antlr
lambda_expression
anonymous_method_expression
anonymous_function_signature
: explicit_anonymous_function_signature
| implicit_anonymous_function_signature
explicit_anonymous_function_signature
explicit_anonymous_function_parameter_list
: explicit_anonymous_function_parameter (','
explicit_anonymous_function_parameter)*
explicit_anonymous_function_parameter
anonymous_function_parameter_modifier
: 'ref'
| 'out'
implicit_anonymous_function_signature
| implicit_anonymous_function_parameter
implicit_anonymous_function_parameter_list
: implicit_anonymous_function_parameter (','
implicit_anonymous_function_parameter)*
implicit_anonymous_function_parameter
: identifier
anonymous_function_body
: expression
| block
Una función anónima con el async modificador es una función asincrónica y sigue las
reglas descritas en funciones asincrónicas.
En una función anónima con un solo parámetro con tipo implícito, los paréntesis se
pueden omitir en la lista de parámetros. En otras palabras, una función anónima con el
formato
C#
C#
C#
C#
int sum = 0;
return sum;
double sum = 0;
foreach (T item in this) sum += selector(item);
return sum;
La ItemList<T> clase tiene dos Sum métodos. Cada toma un selector argumento, que
extrae el valor que se va a sumar de un elemento de lista. El valor extraído puede ser
int o, double y la suma resultante también es int o double .
Los Sum métodos se pueden utilizar, por ejemplo, para calcular las sumas de una lista de
líneas de detalle en un pedido.
C#
class Detail
...
void ComputeSums() {
...
Func<Detail,double> .
Variables externas
Cualquier variable local, parámetro de valor o matriz de parámetros cuyo ámbito incluya
el lambda_expression o anonymous_method_expression se denomina una variable
externa de la función anónima. En un miembro de función de instancia de una clase, el
this valor se considera un parámetro de valor y es una variable externa de cualquier
En el ejemplo
C#
using System;
class Test
static D F() {
int x = 0;
return result;
D d = F();
Console.WriteLine(d());
Console.WriteLine(d());
Console.WriteLine(d());
Consola
Cuando una función anónima captura una variable local o un parámetro de valor, la
variable local o el parámetro ya no se considera una variable fija (variables fijas y
móviles), sino que se considera una variable móvil. Por lo tanto, cualquier unsafe código
que toma la dirección de una variable externa capturada debe usar primero la fixed
instrucción para corregir la variable.
Tenga en cuenta que, a diferencia de una variable no capturada, una variable local
capturada se puede exponer simultáneamente a varios subprocesos de ejecución.
Se considera que se crea una instancia de una variable local cuando la ejecución entra
en el ámbito de la variable. Por ejemplo, cuando se invoca el método siguiente, x se
crea una instancia de la variable local y se inicializa tres veces, una vez para cada
iteración del bucle.
C#
int x = i * 2 + 1;
...
Sin embargo, mover la declaración de x fuera del bucle produce una única creación de
instancias de x :
C#
int x;
x = i * 2 + 1;
...
En el ejemplo
C#
using System;
class Test
int x = i * 2 + 1;
return result;
genera el resultado:
Consola
C#
int x;
x = i * 2 + 1;
return result;
el resultado es:
Consola
Si un bucle for declara una variable de iteración, se considera que esa variable se declara
fuera del bucle. Por lo tanto, si se cambia el ejemplo para capturar la variable de
iteración en sí:
C#
return result;
Consola
C#
int x = 0;
int y = 0;
return result;
los tres delegados capturan la misma instancia de x , pero las instancias independientes
de y , y el resultado es:
Consola
1 1
2 1
3 1
C#
using System;
class Test
int x = 0;
s(5);
Console.WriteLine(g());
s(10);
Console.WriteLine(g());
las dos funciones anónimas capturan la misma instancia de la variable local x y, por
tanto, pueden "comunicarse" a través de esa variable. La salida del ejemplo es:
Consola
10
Expresiones de consulta
Las expresiones de consulta proporcionan una sintaxis integrada de lenguaje para las
consultas que es similar a los lenguajes de consulta jerárquica y relacional, como SQL y
XQuery.
antlr
query_expression
: from_clause query_body
from_clause
query_body
query_body_clauses
: query_body_clause
| query_body_clauses query_body_clause
query_body_clause
: from_clause
| let_clause
| where_clause
| join_clause
| join_into_clause
| orderby_clause
let_clause
where_clause
: 'where' boolean_expression
join_clause
join_into_clause
orderby_clause
: 'orderby' orderings
orderings
ordering
: expression ordering_direction?
ordering_direction
: 'ascending'
| 'descending'
select_or_group_clause
: select_clause
| group_clause
select_clause
: 'select' expression
group_clause
query_continuation
Una expresión de consulta comienza con una from cláusula y termina con una select
group cláusula o. La from cláusula Initial puede ir seguida de cero o más from let
cláusulas,, where join o orderby . Cada from cláusula es un generador que introduce
una *variable de rango _ que va por los elementos de una secuencia _ * * *. Cada let
cláusula presenta una variable de rango que representa un valor calculado por medio de
las variables de rango anteriores. Cada where cláusula es un filtro que excluye
elementos del resultado. Cada join cláusula compara las claves especificadas de la
secuencia de origen con claves de otra secuencia, produciendo pares coincidentes. Cada
orderby cláusula reordena los elementos según los criterios especificados. La select
cláusula final o group especifica la forma del resultado en términos de las variables de
rango. Por último, into se puede usar una cláusula para "Insertar" consultas tratando
los resultados de una consulta como un generador en una consulta posterior.
Para este propósito, una expresión de consulta es cualquier expresión que empiece por
" from identifier " seguido de cualquier token excepto " ; ", " = " o " , ".
Para usar estas palabras como identificadores dentro de una expresión de consulta, se
les puede anteponer " @ " (identificadores).
C#
se traduce en
C#
Las traducciones de las secciones siguientes suponen que las consultas no tienen
ninguna into continuación.
En el ejemplo
C#
from c in customers
se traduce en
C#
from g in
from c in customers
group c by c.Country
C#
customers.
C#
from T x in e
se traduce en
C#
C#
join T x in e on k1 equals k2
se traduce en
C#
Las traducciones de las secciones siguientes suponen que las consultas no tienen tipos
de variable de intervalo explícitos.
En el ejemplo
C#
select c
se traduce en
C#
from c in customers.Cast<Customer>()
select c
C#
customers.
Cast<Customer>().
Los tipos de variables de rango explícitos son útiles para consultar colecciones que
implementan la interfaz no genérica IEnumerable , pero no la IEnumerable<T> interfaz
genérica. En el ejemplo anterior, sería el caso si customers fuera de tipo ArrayList .
C#
from x in e select x
se traduce en
C#
( e ) . Select ( x => x )
En el ejemplo
C#
from c in customers
select c
se traduce en
C#
customers.Select(c => c)
C#
from x1 in e1
from x2 in e2
select v
se traduce en
C#
Expresión de consulta con una segunda from cláusula seguida de un valor distinto de
una select cláusula:
C#
from x1 in e1
from x2 in e2
...
se traduce en
C#
...
C#
from x in e
let y = f
...
se traduce en
C#
...
C#
from x in e
where f
...
se traduce en
C#
...
Expresión de consulta con una join cláusula sin un into seguido de una select
cláusula
C#
from x1 in e1
join x2 in e2 on k1 equals k2
select v
se traduce en
C#
Expresión de consulta con una join cláusula sin un into seguido de un elemento
distinto de una select cláusula
C#
from x1 in e1
join x2 in e2 on k1 equals k2
...
se traduce en
C#
...
Una expresión de consulta con una join cláusula con una into cláusula seguida de una
select cláusula
C#
from x1 in e1
select v
se traduce en
C#
Una expresión de consulta con una join cláusula con un into seguido de un elemento
distinto de una select cláusula
C#
from x1 in e1
...
se traduce en
C#
from * in ( e1 ) . GroupJoin( e2 , x1 => k1 , x2 => k2 , ( x1 , g ) => new {
x1 , g })
...
C#
from x in e
orderby k1 , k2 , ..., kn
...
se traduce en
C#
from x in ( e ) .
OrderBy ( x => k1 ) .
ThenBy ( x => k2 ) .
... .
ThenBy ( x => kn )
...
Las siguientes traducciones suponen que no let hay where join orderby cláusulas, o,
ni más de una from cláusula inicial en cada expresión de consulta.
En el ejemplo
C#
from c in customers
from o in c.Orders
se traduce en
C#
customers.
En el ejemplo
C#
from c in customers
from o in c.Orders
se traduce en
C#
from * in customers.
C#
customers.
En el ejemplo
C#
from o in orders
se traduce en
C#
from * in orders.
C#
orders.
En el ejemplo
C#
from c in customers
se traduce en
C#
En el ejemplo
C#
from c in customers
let n = co.Count()
where n >= 10
se traduce en
C#
from * in customers.
let n = co.Count()
where n >= 10
C#
customers.
En el ejemplo
C#
from o in orders
select o
C#
orders.
Cláusulas Select
Una expresión de consulta con el formato
C#
from x in e select v
se traduce en
C#
( e ) . Select ( x => v )
( e )
Por ejemplo
C#
select c
simplemente se traduce en
C#
Cláusulas GroupBy
C#
from x in e group v by k
se traduce en
C#
C#
( e ) . GroupBy ( x => k )
En el ejemplo
C#
from c in customers
se traduce en
C#
customers.
Identificadores transparentes
Ciertas traducciones insertan variables de intervalo con *identificadores transparentes _
indicados por _ . Los identificadores transparentes no son una característica de lenguaje
adecuada; solo existen como un paso intermedio en el proceso de conversión de
expresiones de consulta.
En el ejemplo
C#
from c in customers
from o in c.Orders
se traduce en
C#
from * in customers.
que se traduce en
C#
customers.
C#
customers.
En el ejemplo
C#
from c in customers
se traduce en
C#
from * in customers.
C#
customers.
C#
customers.
class C
class C<T> : C
Func<T,U,V> resultSelector);
Func<T,E> elementSelector);
-una secuencia de secuencias, donde cada secuencia interna tiene una propiedad
adicional Key .
Operadores de asignación
Los operadores de asignación asignan un nuevo valor a una variable, una propiedad, un
evento o un elemento de indexador.
antlr
assignment
assignment_operator
: '='
| '+='
| '-='
| '*='
| '/='
| '%='
| '&='
| '|='
| '^='
| '<<='
| right_shift_assignment
El operando izquierdo de una asignación debe ser una expresión clasificada como una
variable, un acceso de propiedad, un acceso de indexador o un acceso de evento.
Los operadores de asignación son asociativos a la derecha, lo que significa que las
operaciones se agrupan de derecha a izquierda. Por ejemplo, una expresión con el
formato a = b = c se evalúa como a = (b = c) .
Asignación simple
El = operador se denomina operador de asignación simple.
Si el operando izquierdo de una asignación simple tiene el formato E.P o E[Ei] donde
E tiene el tipo en tiempo de compilación dynamic , la asignación está enlazada
En una asignación simple, el operando derecho debe ser una expresión que se pueda
convertir implícitamente al tipo del operando izquierdo. La operación asigna el valor del
operando derecho a la variable, la propiedad o el elemento indexador proporcionado
por el operando izquierdo.
C#
object[] oa = sa;
oa[0] = null; // Ok
oa[1] = "Hello"; // Ok
C#
struct Point
int x, y;
this.x = x;
this.y = y;
public int X {
get { return x; }
set { x = value; }
public int Y {
get { return y; }
set { y = value; }
struct Rectangle
Point a, b;
this.a = a;
this.b = b;
public Point A {
get { return a; }
set { a = value; }
public Point B {
get { return b; }
set { b = value; }
en el ejemplo
C#
p.X = 100;
p.Y = 100;
r.B = p;
las asignaciones a p.X , p.Y , r.A y r.B se permiten porque p y r son variables. Sin
embargo, en el ejemplo
C#
r.A.X = 10;
r.A.Y = 10;
r.B.X = 100;
r.B.Y = 100;
Asignación compuesta
Si el operando izquierdo de una asignación compuesta tiene el formato E.P o E[Ei]
donde E tiene el tipo en tiempo de compilación dynamic , la asignación está enlazada
dinámicamente (enlace dinámico). En este caso, el tipo en tiempo de compilación de la
expresión de asignación es dynamic , y la resolución que se describe a continuación se
realizará en tiempo de ejecución en función del tipo en tiempo de ejecución de E .
C#
byte b = 0;
char ch = '\0';
int i = 0;
b += 1; // Ok
b += (byte)i; // Ok
ch += (char)1; // Ok
la razón intuitiva de cada error es que una asignación simple correspondiente también
habría sido un error.
C#
int? i = 0;
i += 1; // Ok
Asignación de eventos
Si el operando izquierdo de += un -= operador Or se clasifica como un acceso de
evento, la expresión se evalúa como sigue:
Expression
Una expresión es una non_assignment_expression o una asignación.
antlr
expression
: non_assignment_expression
| assignment
non_assignment_expression
: conditional_expression
| lambda_expression
| query_expression
Expresiones constantes
Una constant_expression es una expresión que se puede evaluar por completo en tiempo
de compilación.
antlr
constant_expression
: expression
Una expresión constante debe ser el null literal o un valor con uno de los siguientes
tipos: sbyte , byte , short , ushort , int , uint , long , ulong , char , float , double ,
decimal ,,, bool object string o cualquier tipo de enumeración. Solo se permiten las
Conversiones de identidad
Conversiones numéricas
Conversiones de enumeración
Conversiones de expresiones constantes
Conversiones de referencia implícitas y explícitas, siempre que el origen de las
conversiones sea una expresión constante que se evalúe como el valor null.
class C {
Expresiones booleanas
Un Boolean_expression es una expresión que produce un resultado de tipo bool , ya sea
directamente o a través de operator true la aplicación de en determinados contextos,
tal y como se especifica en el siguiente.
antlr
boolean_expression
: expression
antlr
statement
: labeled_statement
| declaration_statement
| embedded_statement
embedded_statement
: block
| empty_statement
| expression_statement
| selection_statement
| iteration_statement
| jump_statement
| try_statement
| checked_statement
| unchecked_statement
| lock_statement
| using_statement
| yield_statement
| embedded_statement_unsafe
C#
void F(bool b) {
if (b)
int i = 44;
En el ejemplo
C#
void F() {
Console.WriteLine("reachable");
goto Label;
Console.WriteLine("unreachable");
Label:
Console.WriteLine("reachable");
En el ejemplo
C#
void F() {
const int i = 1;
if (i == 2) Console.WriteLine("unreachable");
C#
void F() {
int i = 1;
if (i == 2) Console.WriteLine("reachable");
En el ejemplo
C#
void F(int x) {
Console.WriteLine("start");
if (x < 0) Console.WriteLine("negative");
Hay dos situaciones en las que se trata de un error en tiempo de compilación para que
el punto final de una instrucción sea alcanzable:
Dado que la switch instrucción no permite que una sección switch pase a la
siguiente sección switch, se trata de un error en tiempo de compilación para que el
punto final de la lista de instrucciones de una sección switch sea accesible. Si se
produce este error, normalmente es una indicación de que break falta una
instrucción.
Es un error en tiempo de compilación el punto final del bloque de un miembro de
función que calcula un valor al que se puede tener acceso. Si se produce este error,
normalmente es una indicación de que return falta una instrucción.
Blocks
Un bloque permite que se escriban varias instrucciones en contextos donde se permite
una única instrucción.
antlr
block
Listas de instrucciones
Una *lista de instrucciones _ consta de una o varias instrucciones escritas en secuencia.
Las listas de instrucciones se producen en _block * s (bloques) y en Switch_block s (la
instrucción switch).
antlr
statement_list
: statement+
antlr
empty_statement
: ';'
Se utiliza una instrucción vacía cuando no hay ninguna operación que realizar en un
contexto donde se requiere una instrucción.
Se puede usar una instrucción vacía al escribir una while instrucción con un cuerpo
nulo:
C#
void ProcessMessages() {
while (ProcessMessage())
Además, se puede usar una instrucción vacía para declarar una etiqueta justo antes del
cierre " } " de un bloque:
C#
void F() {
...
...
exit: ;
antlr
labeled_statement
Una instrucción con etiqueta declara una etiqueta con el nombre proporcionado por el
identificador. El ámbito de una etiqueta es el bloque entero en el que se declara la
etiqueta, incluidos los bloques anidados. Es un error en tiempo de compilación que dos
etiquetas con el mismo nombre tienen ámbitos superpuestos.
Se puede hacer referencia a una etiqueta desde goto instrucciones (instrucción Goto)
dentro del ámbito de la etiqueta. Esto significa que las goto instrucciones pueden
transferir el control dentro de los bloques y fuera de los bloques, pero nunca a los
bloques.
C#
int F(int x) {
if (x >= 0) goto x;
x = -x;
x: return x;
punto final del finally bloque es inaccesible, la instrucción con etiqueta no es accesible
desde esa goto instrucción).
Instrucciones de declaración
Un declaration_statement declara una variable o constante local. Las instrucciones de
declaración se permiten en bloques, pero no se permiten como instrucciones
incrustadas.
antlr
declaration_statement
: local_variable_declaration ';'
| local_constant_declaration ';'
antlr
local_variable_declaration
: local_variable_type local_variable_declarators
local_variable_type
: type
| 'var'
local_variable_declarators
: local_variable_declarator
local_variable_declarator
: identifier
local_variable_initializer
: expression
| array_initializer
| local_variable_initializer_unsafe
En el contexto de una declaración de variable local, el identificador var actúa como una
palabra clave contextual (palabras clave). Cuando el local_variable_type se especifica
como var y ningún tipo denominado var está en el ámbito, la declaración es una
declaración de variable local con tipo implícito, cuyo tipo se deduce del tipo de la
expresión de inicializador asociada. Las declaraciones de variables locales con tipo
implícito están sujetas a las siguientes restricciones:
C#
Una declaración de variable local que declara varias variables es equivalente a varias
declaraciones de variables únicas con el mismo tipo. Además, un inicializador de
variable en una declaración de variable local se corresponde exactamente con una
instrucción de asignación que se inserta inmediatamente después de la declaración.
En el ejemplo
C#
void F() {
int x = 1, y, z = x * 2;
corresponde exactamente a
C#
void F() {
int x; x = 1;
int y;
int z; z = x * 2;
En una declaración de variable local con tipo implícito, el tipo de la variable local que se
está declarando se toma como el mismo tipo de la expresión que se usa para inicializar
la variable. Por ejemplo:
C#
var i = 5;
var s = "Hello";
var d = 1.0;
Las declaraciones de variables locales con tipo implícito anterior son exactamente
equivalentes a las siguientes declaraciones con tipo explícito:
C#
int i = 5;
string s = "Hello";
double d = 1.0;
antlr
local_constant_declaration
constant_declarators
constant_declarator
Una declaración de constante local que declara varias constantes es equivalente a varias
declaraciones de constantes únicas con el mismo tipo.
Instrucciones de expresión
Un expression_statement evalúa una expresión determinada. El valor calculado por la
expresión, si existe, se descarta.
antlr
expression_statement
: statement_expression ';'
statement_expression
: invocation_expression
| null_conditional_invocation_expression
| object_creation_expression
| assignment
| post_increment_expression
| post_decrement_expression
| pre_increment_expression
| pre_decrement_expression
| await_expression
Instrucciones de selección
Las instrucciones de selección seleccionan una de varias instrucciones posibles para su
ejecución según el valor de alguna expresión.
antlr
selection_statement
: if_statement
| switch_statement
Instrucción If
La if instrucción selecciona una instrucción para su ejecución basada en el valor de una
expresión booleana.
antlr
if_statement
es equivalente a
C#
if (x) {
if (y) {
F();
else {
G();
El punto final de una if instrucción es accesible si el punto final de al menos una de sus
instrucciones incrustadas es accesible. Además, el punto final de una if instrucción sin
ninguna else parte es accesible si la if instrucción es accesible y la expresión booleana
no tiene el valor constante true .
La instrucción switch
La instrucción switch selecciona la ejecución de una lista de instrucciones que tiene una
etiqueta de conmutador asociada que corresponde al valor de la expresión switch.
antlr
switch_statement
switch_block
switch_section
: switch_label+ statement_list
switch_label
| 'default' ':'
La expresión constante de cada case etiqueta debe indicar un valor que se pueda
convertir implícitamente (conversiones implícitas) en el tipo aplicable de la switch
instrucción. Se produce un error en tiempo de compilación si dos o más case etiquetas
de la misma switch instrucción especifican el mismo valor constante.
Puede haber como máximo una default etiqueta en una instrucción switch.
C#
switch (i) {
case 0:
CaseZero();
break;
case 1:
CaseOne();
break;
default:
CaseOthers();
break;
es válido porque ninguna sección del modificador tiene un punto final alcanzable. A
diferencia de C y C++, no se permite la ejecución de una sección switch a la siguiente
sección switch y el ejemplo
C#
switch (i) {
case 0:
CaseZero();
case 1:
CaseZeroOrOne();
default:
CaseAny();
C#
switch (i) {
case 0:
CaseZero();
goto case 1;
case 1:
CaseZeroOrOne();
goto default;
default:
CaseAny();
break;
C#
switch (i) {
case 0:
CaseZero();
break;
case 1:
CaseOne();
break;
case 2:
default:
CaseTwo();
break;
es válido. En el ejemplo no se infringe la regla "no hay que pasar" porque las etiquetas
case 2: y default: forman parte del mismo switch_section.
La regla "no pasar" evita una clase común de errores que se producen en C y C++
cuando las break instrucciones se omiten accidentalmente. Además, debido a esta
regla, las secciones switch de una switch instrucción se pueden reorganizar
arbitrariamente sin afectar al comportamiento de la instrucción. Por ejemplo, las
secciones de la switch instrucción anterior se pueden revertir sin afectar al
comportamiento de la instrucción:
C#
switch (i) {
default:
CaseAny();
break;
case 1:
CaseZeroOrOne();
goto default;
case 0:
CaseZero();
goto case 1;
La lista de instrucciones de una sección switch normalmente finaliza en break una goto
case instrucción, o goto default , pero se permite cualquier construcción que
C#
switch (i) {
case 0:
case 1:
case 2:
return;
El tipo de control de una switch instrucción puede ser el tipo string . Por ejemplo:
C#
switch (command.ToLower()) {
case "run":
DoRun();
break;
case "save":
DoSave();
break;
case "quit":
DoQuit();
break;
default:
InvalidCommand(command);
break;
El punto final de una switch instrucción es accesible si se cumple al menos una de las
siguientes condiciones:
antlr
iteration_statement
: while_statement
| do_statement
| for_statement
| foreach_statement
La instrucción while
La while instrucción ejecuta condicionalmente una instrucción incrustada cero o más
veces.
antlr
while_statement
Dentro de la instrucción incrustada de una while instrucción, break se puede usar una
instrucción (la instrucción break) para transferir el control al punto final de la while
instrucción (por lo tanto, finalizar la iteración de la instrucción incrustada) y continue se
puede usar una instrucción (la instrucción continue) para transferir el control al punto
final de la instrucción incrustada (por lo que se realiza otra iteración de la while
instrucción
La while instrucción contiene una instrucción accesible break que sale de la while
instrucción.
La while instrucción es accesible y la expresión booleana no tiene el valor
constante true .
La instrucción do
La do instrucción ejecuta condicionalmente una instrucción incrustada una o varias
veces.
antlr
do_statement
La instrucción for
La for instrucción evalúa una secuencia de expresiones de inicialización y, a
continuación, mientras una condición es true, ejecuta repetidamente una instrucción
incrustada y evalúa una secuencia de expresiones de iteración.
antlr
for_statement
for_initializer
: local_variable_declaration
| statement_expression_list
for_condition
: boolean_expression
for_iterator
: statement_expression_list
statement_expression_list
Dentro de la instrucción incrustada de una for instrucción, break se puede usar una
instrucción (la instrucción break) para transferir el control al punto final de la for
instrucción (con lo que finaliza la iteración de la instrucción incrustada) y continue se
puede usar una instrucción (la instrucción continue) para transferir el control al punto
final de la instrucción incrustada (de modo que se ejecute el for_iterator y realice otra
iteración de la for instrucción, empezando por el for_condition)
El punto final de una for instrucción es accesible si se cumple al menos una de las
siguientes condiciones:
La for instrucción contiene una instrucción accesible break que sale de la for
instrucción.
La for instrucción es accesible y hay un for_condition presente y no tiene el valor
constante true .
La instrucción foreach
La foreach instrucción enumera los elementos de una colección y ejecuta una
instrucción incrustada para cada elemento de la colección.
antlr
foreach_statement
C#
foreach (V v in x) embedded_statement
C#
E e = ((C)(x)).GetEnumerator();
try {
while (e.MoveNext()) {
V v = (V)(T)e.Current;
embedded_statement
finally {
... // Dispose e
Por ejemplo:
C#
int[] values = { 7, 9, 13 };
Action f = null;
f();
Si v se declaró fuera del bucle while, se compartirá entre todas las iteraciones y su valor
después del bucle for sería el valor final, 13 , que es lo que la invocación de f
imprimiría. En su lugar, dado que cada iteración tiene su propia variable v , la f que
captura en la primera iteración seguirá conservando el valor 7 , que es lo que se va a
imprimir. (Nota: versiones anteriores de C# declaradas v fuera del bucle while).
C#
finally {
((System.IDisposable)e).Dispose();
C#
finally {
if (e != null) ((System.IDisposable)e).Dispose();
C#
finally {
C#
finally {
System.IDisposable d = e as System.IDisposable;
if (d != null) d.Dispose();
C#
using System;
class Test
double[,] values = {
};
Console.WriteLine();
Consola
En el ejemplo
C#
int[] numbers = { 1, 3, 5, 7, 9 };
Instrucciones de salto
Las instrucciones de salto transfieren el control incondicionalmente.
antlr
jump_statement
: break_statement
| continue_statement
| goto_statement
| return_statement
| throw_statement
En el ejemplo
C#
using System;
class Test
while (true) {
try {
try {
Console.WriteLine("Before break");
break;
finally {
finally {
Console.WriteLine("After break");
los finally bloques asociados a dos try instrucciones se ejecutan antes de que el
control se transfiera al destino de la instrucción de salto.
Consola
Before break
After break
Instrucción break
La break instrucción sale de la instrucción de inclusión,,, o más cercana switch while
do for foreach .
antlr
break_statement
: 'break' ';'
Cuando varias switch instrucciones,,, while do for o foreach se anidan entre sí, una
break instrucción solo se aplica a la instrucción más interna. Para transferir el control
entre varios niveles de anidamiento, goto se debe usar una instrucción (instrucción
Goto).
Una break instrucción no puede salir de un finally bloque (la instrucción try). Cuando
una break instrucción aparece dentro de un finally bloque, el destino de la break
instrucción debe estar dentro del mismo finally bloque; de lo contrario, se produce un
error en tiempo de compilación.
Si la break instrucción sale de uno o más try bloques con finally bloques
asociados, el control se transfiere inicialmente al finally bloque de la instrucción
más interna try . Cuando y si el control alcanza el punto final de un finally
bloque, el control se transfiere al finally bloque de la siguiente instrucción de
inclusión try . Este proceso se repite hasta que finally se hayan ejecutado los
bloques de todas las instrucciones que intervienen try .
El control se transfiere al destino de la break instrucción.
Dado break que una instrucción transfiere el control incondicionalmente a otra parte, el
punto final de una break instrucción nunca es accesible.
La instrucción continue
La continue instrucción inicia una nueva iteración de la instrucción envolvente while ,
do , for o más cercana foreach .
antlr
continue_statement
: 'continue' ';'
Cuando varias while do instrucciones,, for o foreach se anidan entre sí, una continue
instrucción solo se aplica a la instrucción más interna. Para transferir el control entre
varios niveles de anidamiento, goto se debe usar una instrucción (instrucción Goto).
Una continue instrucción no puede salir de un finally bloque (la instrucción try).
Cuando una continue instrucción aparece dentro de un finally bloque, el destino de la
continue instrucción debe estar dentro del mismo finally bloque; de lo contrario, se
Si la continue instrucción sale de uno o más try bloques con finally bloques
asociados, el control se transfiere inicialmente al finally bloque de la instrucción
más interna try . Cuando y si el control alcanza el punto final de un finally
bloque, el control se transfiere al finally bloque de la siguiente instrucción de
inclusión try . Este proceso se repite hasta que finally se hayan ejecutado los
bloques de todas las instrucciones que intervienen try .
El control se transfiere al destino de la continue instrucción.
La instrucción goto
La goto instrucción transfiere el control a una instrucción marcada por una etiqueta.
antlr
goto_statement
C#
using System;
class Test
string[,] table = {
};
if (str == table[row,colm])
goto done;
continue;
done:
una goto instrucción se usa para transferir el control fuera de un ámbito anidado.
Una goto instrucción no puede salir de un finally bloque (la instrucción try). Cuando
una goto instrucción aparece dentro de un finally bloque, el destino de la goto
instrucción debe estar dentro del mismo finally bloque o, de lo contrario, se producirá
un error en tiempo de compilación.
Si la goto instrucción sale de uno o más try bloques con finally bloques
asociados, el control se transfiere inicialmente al finally bloque de la instrucción
más interna try . Cuando y si el control alcanza el punto final de un finally
bloque, el control se transfiere al finally bloque de la siguiente instrucción de
inclusión try . Este proceso se repite hasta que finally se hayan ejecutado los
bloques de todas las instrucciones que intervienen try .
El control se transfiere al destino de la goto instrucción.
Dado goto que una instrucción transfiere el control incondicionalmente a otra parte, el
punto final de una goto instrucción nunca es accesible.
La instrucción return
La return instrucción devuelve el control al llamador actual de la función en la que
return aparece la instrucción.
antlr
return_statement
Una return instrucción sin expresión solo se puede usar en un miembro de función que
no calcule un valor, es decir, un método con el tipo de resultado (cuerpo del método)
void , el set descriptor de acceso de una propiedad o un indizador, y descriptores add
remove de acceso de un evento, un constructor de instancia, un constructor estático o
un destructor.
Una return instrucción con una expresión solo se puede usar en un miembro de
función que calcula un valor, es decir, un método con un tipo de resultado no void, el
get descriptor de acceso de una propiedad o un indizador, o un operador definido por
el usuario. Debe existir una conversión implícita (conversiones implícitas) del tipo de la
expresión al tipo de valor devuelto del miembro de función contenedora.
Dado return que una instrucción transfiere el control incondicionalmente a otra parte,
el punto final de una return instrucción nunca es accesible.
La instrucción throw
La instrucción throw genera una excepción.
antlr
throw_statement
Una throw instrucción con una expresión inicia el valor generado al evaluar la expresión.
La expresión debe indicar un valor del tipo de clase System.Exception , de un tipo de
clase que se deriva de System.Exception o de un tipo de parámetro de tipo que tiene
System.Exception (o una subclase de ella) como su clase base efectiva. Si la evaluación
Una throw instrucción sin expresión solo se puede usar en un catch bloque, en cuyo
caso la instrucción vuelve a iniciar la excepción que está controlando actualmente ese
catch bloque.
Dado throw que una instrucción transfiere el control incondicionalmente a otra parte, el
punto final de una throw instrucción nunca es accesible.
Instrucción try
La try instrucción proporciona un mecanismo para detectar las excepciones que se
producen durante la ejecución de un bloque. Además, la try instrucción proporciona la
capacidad de especificar un bloque de código que siempre se ejecuta cuando el control
sale de la try instrucción.
antlr
try_statement
catch_clause
exception_specifier
exception_filter
finally_clause
: 'finally' block
de tipo que tiene System.Exception (o una subclase de ella) como su clase base efectiva.
Dentro de un catch bloque, throw se puede usar una instrucción (instrucción throw) sin
expresión para volver a producir la excepción detectada por el catch bloque. Las
asignaciones a una variable de excepción no modifican la excepción que se vuelve a
iniciar.
En el ejemplo
C#
using System;
class Test
try {
G();
catch (Exception e) {
e = new Exception("F");
throw; // re-throw
try {
F();
catch (Exception e) {
Exception in F: G
Exception in Main: G
Consola
Exception in F: G
Exception in Main: F
Es un error en tiempo de compilación para una break continue instrucción, o goto para
transferir el control fuera de un finally bloque. Cuando una break continue
instrucción, o goto aparece en un finally bloque, el destino de la instrucción debe
estar dentro del mismo finally bloque o, de lo contrario, se produce un error en
tiempo de compilación.
catch Se puede tener acceso a un bloque de una try instrucción si la try instrucción es
accesible.
El punto final de una try instrucción es accesible si se cumplen las dos condiciones
siguientes:
El punto final del try bloque es accesible o catch se puede tener acceso al punto
final de al menos un bloque.
Si finally hay un bloque, finally se puede tener acceso al punto final del
bloque.
antlr
checked_statement
: 'checked' block
unchecked_statement
: 'unchecked' block
;
La checked instrucción hace que todas las expresiones del bloque se evalúen en un
contexto comprobado, y la unchecked instrucción hace que todas las expresiones del
bloque se evalúen en un contexto no comprobado.
lock (instrucción)
La lock instrucción obtiene el bloqueo de exclusión mutua para un objeto determinado,
ejecuta una instrucción y, a continuación, libera el bloqueo.
antlr
lock_statement
La expresión de una lock instrucción debe indicar un valor de un tipo conocido como
reference_type. No se realiza ninguna conversión boxing implícita (conversiones boxing)
en la expresión de una lock instrucción y, por lo tanto, es un error en tiempo de
compilación para que la expresión denote un valor de un value_type.
Una lock instrucción con el formato
C#
C#
try {
...
finally {
if (__lockWasTaken) System.Threading.Monitor.Exit(x);
datos estáticos. Otro código podría bloquearse en el mismo tipo, lo que puede provocar
un interbloqueo. Un mejor enfoque es sincronizar el acceso a los datos estáticos
bloqueando un objeto estático privado. Por ejemplo:
C#
class Cache
lock (Cache.synchronizationObject) {
...
lock (Cache.synchronizationObject) {
...
La instrucción using
La using instrucción obtiene uno o más recursos, ejecuta una instrucción y, a
continuación, desecha el recurso.
antlr
using_statement
resource_acquisition
: local_variable_declaration
| expression
Una using instrucción se traduce en tres partes: adquisición, uso y eliminación. El uso
del recurso se adjunta implícitamente en una try instrucción que incluye una finally
cláusula. Esta finally cláusula desecha el recurso. Si null se adquiere un recurso, no se
realiza ninguna llamada a Dispose y no se produce ninguna excepción. Si el recurso es
de tipo dynamic , se convierte dinámicamente a través de una conversión dinámica
implícita (conversiones dinámicas implícitas) en IDisposable durante la adquisición para
asegurarse de que la conversión se realiza correctamente antes del uso y la eliminación.
C#
C#
try {
statement;
finally {
((IDisposable)resource).Dispose();
C#
try {
statement;
finally {
C#
IDisposable d = (IDisposable)resource;
try {
statement;
finally {
if (d != null) d.Dispose();
C#
C#
C#
...
statement
C#
using System;
using System.IO;
class Test
string s;
Console.WriteLine(s);
Yield (instrucción)
La yield instrucción se usa en un bloque de iteradores (bloques) para obtener un valor
para el objeto enumerador (objetos enumerador) o el objeto enumerable (objetos
enumerables) de un iterador o para señalar el final de la iteración.
antlr
yield_statement
yield no es una palabra reservada; tiene un significado especial solo cuando se usa
inmediatamente antes de una return break palabra clave o. En otros contextos, yield
se puede usar como identificador.
Hay varias restricciones sobre dónde yield puede aparecer una instrucción, tal y como
se describe a continuación.
C#
IEnumerator<int> GetEnumerator() {
try {
yield return 1; // Ok
yield break; // Ok
finally {
try {
yield break; // Ok
catch {
yield break; // Ok
D d = delegate {
};
int MyMethod() {
Debe existir una conversión implícita (conversiones implícitas) del tipo de la expresión
en la yield return instrucción al tipo yield (yield Type) del iterador.
Si la yield break instrucción está delimitada por uno o más try bloques con
finally bloques asociados, el control se transfiere inicialmente al finally bloque
Dado yield break que una instrucción transfiere el control incondicionalmente a otra
parte, el punto final de una yield break instrucción nunca es accesible.
Espacios de nombres
Artículo • 16/09/2021 • Tiempo de lectura: 18 minutos
Las directivas Using (directivas Using) se proporcionan para facilitar el uso de espacios
de nombres.
Unidades de compilación
Un compilation_unit define la estructura global de un archivo de código fuente. Una
unidad de compilación consta de cero o más using_directive s seguidos de cero o más
global_attributes seguidos de cero o más namespace_member_declaration s.
antlr
compilation_unit
C#
class A {}
Archivo B.cs :
C#
class B {}
antlr
namespace_declaration
qualified_identifier
namespace_body
C#
namespace N1.N2
class A {}
class B {}
es semánticamente equivalente a
C#
namespace N1
namespace N2
class A {}
class B {}
namespace N1.N2
class A {}
namespace N1.N2
class B {}
Alias externos
Una extern_alias_directive introduce un identificador que actúa como un alias para un
espacio de nombres. La especificación del espacio de nombres con alias es externa al
código fuente del programa y se aplica también a los espacios de nombres anidados del
espacio de nombres con alias.
antlr
extern_alias_directive
El programa siguiente declara y usa dos alias extern, X y Y , cada uno de los cuales
representa la raíz de una jerarquía de espacios de nombres distinta:
C#
extern alias X;
extern alias Y;
class Test
X::N.A a;
X::N.B b1;
Y::N.B b2;
Y::N.C c;
El programa declara la existencia de los alias extern X y Y , pero las definiciones reales
de los alias son externas al programa. N.B Ahora se puede hacer referencia a las clases
con el mismo nombre como X.N.B y Y.N.B , o mediante el calificador de alias del
espacio de nombres, X::N.B y Y::N.B . Se produce un error si un programa declara un
alias extern para el que no se proporciona ninguna definición externa.
Directivas Using
*El uso de directivas _ facilita el uso de espacios de nombres y tipos definidos en otros
espacios de nombres. Las directivas de uso afectan al proceso de resolución de nombres
de _namespace_or_type_name * s (nombres de espacio de nombres y tipos) y
simple_name s (nombres simples), pero a diferencia de las declaraciones, las directivas
Using no aportan nuevos miembros a los espacios de declaración subyacentes de las
unidades de compilación o los espacios de nombres en los que se utilizan.
antlr
using_directive
: using_alias_directive
| using_namespace_directive
| using_static_directive
antlr
using_alias_directive
C#
namespace N1.N2
class A {}
namespace N3
using A = N1.N2.A;
class B: A {}
C#
namespace N3
using R = N1.N2;
class B: R.A {}
C#
namespace N3
class A {}
namespace N3
C#
namespace N3
using R = N1.N2;
namespace N3
C#
using R = N1.N2;
namespace N3
class B: R.A {}
namespace N3
class C: R.A {}
Al igual que los miembros normales, los nombres introducidos por using_alias_directive
s están ocultos por miembros con el mismo nombre en ámbitos anidados. En el ejemplo
C#
using R = N1.N2;
namespace N3
class R {}
C#
namespace N1.N2 {}
namespace N3
extern alias E;
using R1 = E.N; // OK
using R2 = N1; // OK
using R3 = N1.N2; // OK
C#
namespace N1.N2
class A {}
namespace N3
using R1 = N1;
using R2 = N1.N2;
class B
los nombres N1.N2.A , R1.N2.A y R2.A son equivalentes y todos hacen referencia a la
clase cuyo nombre completo es N1.N2.A .
El uso de alias puede asignar un nombre a un tipo construido cerrado, pero no puede
asignar un nombre a una declaración de tipo genérico sin enlazar sin proporcionar
argumentos de tipo. Por ejemplo:
C#
namespace N1
class A<T>
class B {}
namespace N2
antlr
using_namespace_directive
C#
namespace N1.N2
class A {}
namespace N3
using N1.N2;
class B: A {}
C#
namespace N1.N2
class A {}
namespace N3
using N1;
C#
namespace N1.N2
class A {}
class B {}
namespace N3
using N1.N2;
class A {}
C#
namespace N1
class A {}
namespace N2
class A {}
namespace N3
using N1;
using N2;
C#
namespace N3
using N1;
using N2;
using A = N1.A;
C#
namespace N1
class A {}
class C
namespace N2
using N1;
using static C;
class B
void M()
antlr
using_static_directive
C#
namespace N1
class A
namespace N2
class C
C#
namespace N1
static class A
namespace N2
class B
void N()
TODO: ejemplo
antlr
namespace_member_declaration
: namespace_declaration
| type_declaration
Declaraciones de tipos
Una type_declaration es una class_declaration (declaraciones de clase), una
struct_declaration (declaraciones de struct), una interface_declaration (declaraciones de
interfaz), una enum_declaration (declaraciones de enumeración) o una
delegate_declaration (declaraciones de delegado).
antlr
type_declaration
: class_declaration
| struct_declaration
| interface_declaration
| enum_declaration
| delegate_declaration
Cuando una declaración de tipos para un tipo T se produce como una declaración de
nivel superior en una unidad de compilación, el nombre completo del tipo recién
declarado es simplemente T . Cuando se produce una declaración de tipos para un tipo
T dentro de un espacio de nombres, clase o struct, el nombre completo del tipo recién
declarado es N.T , donde N es el nombre completo del espacio de nombres, la clase o
el struct que lo contiene.
Un tipo declarado dentro de una clase o struct se denomina tipo anidado (tipos
anidados).
antlr
qualified_alias_member
N::I<A1, ..., Ak> , donde N y I representan los identificadores, y <A1, ..., Ak>
Tenga en cuenta que si se usa el calificador de alias de espacio de nombres con un alias
que hace referencia a un tipo, se produce un error en tiempo de compilación. Tenga en
cuenta también que si el identificador N es global , la búsqueda se realiza en el
espacio de nombres global, incluso si hay un alias Using que se asocia global con un
tipo o un espacio de nombres.
Unicidad de alias
Cada unidad de compilación y cuerpo del espacio de nombres tiene un espacio de
declaración independiente para los alias extern y el uso de alias. Por lo tanto, si bien el
nombre de un alias extern o de uso de alias debe ser único en el conjunto de alias
extern y usar alias declarados en la unidad de compilación o el cuerpo del espacio de
nombres que contiene inmediatamente, se permite que un alias tenga el mismo nombre
que un tipo o un espacio de nombres, siempre y cuando se use solo con el ::
calificador.
En el ejemplo
C#
namespace N
public class A {}
public class B {}
namespace N
using A = System.IO;
class X
A::Stream s2; // Ok
Una clase es una estructura de datos que puede contener miembros de datos
(constantes y campos), miembros de función (métodos, propiedades, eventos,
indizadores, operadores, constructores de instancias, destructores y constructores
estáticos) y tipos anidados. Los tipos de clase admiten la herencia, un mecanismo
mediante el cual una clase derivada puede extender y especializar una clase base.
Declaraciones de clase
Una class_declaration es una type_declaration (declaraciones de tipos) que declara una
nueva clase.
antlr
class_declaration
Modificadores de clase
Un class_declaration puede incluir opcionalmente una secuencia de modificadores de
clase:
antlr
class_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'abstract'
| 'sealed'
| 'static'
| class_modifier_unsafe
El new modificador se permite en las clases anidadas. Especifica que la clase oculta un
miembro heredado con el mismo nombre, tal y como se describe en el modificador
New. Se trata de un error en tiempo de compilación para new que el modificador
aparezca en una declaración de clase que no es una declaración de clase anidada.
Clases abstractas
El abstract modificador se usa para indicar que una clase está incompleta y que está
destinada a usarse solo como una clase base. Una clase abstracta es distinta de una
clase no abstracta de las siguientes maneras:
Cuando una clase no abstracta se deriva de una clase abstracta, la clase no abstracta
debe incluir las implementaciones reales de todos los miembros abstractos heredados,
con lo que se reemplazan los miembros abstractos. En el ejemplo
C#
abstract class A
abstract class B: A
class C: B
// actual implementation of F
Clases selladas
El sealed modificador se usa para evitar la derivación de una clase. Se produce un error
en tiempo de compilación si se especifica una clase sellada como la clase base de otra
clase.
Clases estáticas
El static modificador se usa para marcar la clase que se declara como una clase
estática. No se puede crear una instancia de una clase estática, no se puede usar como
un tipo y solo puede contener miembros estáticos. Solo una clase estática puede
contener declaraciones de métodos de extensión (métodos de extensión).
Una clase estática solo puede contener miembros estáticos (miembros estáticos y
de instancia). Tenga en cuenta que las constantes y los tipos anidados se clasifican
como miembros estáticos.
Una clase estática no puede tener miembros con protected o protected internal
declarar accesibilidad.
Unmodifier (modificador)
El partial modificador se usa para indicar que este class_declaration es una declaración
de tipos parciales. Varias declaraciones de tipos parciales con el mismo nombre dentro
de una declaración de tipo o espacio de nombres envolvente se combinan para formar
una declaración de tipos, siguiendo las reglas especificadas en tipos parciales.
Tener la declaración de una clase distribuida sobre segmentos independientes del texto
del programa puede ser útil si estos segmentos se producen o mantienen en contextos
diferentes. Por ejemplo, una parte de una declaración de clase puede ser generada por
el equipo, mientras que la otra se crea manualmente. La separación textual de los dos
impide que las actualizaciones se realicen en conflicto con las actualizaciones del otro.
Parámetros de tipo
Un parámetro de tipo es un identificador simple que denota un marcador de posición
para un argumento de tipo proporcionado para crear un tipo construido. Un parámetro
de tipo es un marcador de posición formal para un tipo que se proporcionará más
adelante. Por el contrario, un argumento de tipo (argumentos de tipo) es el tipo real que
se sustituye por el parámetro de tipo cuando se crea un tipo construido.
antlr
type_parameter_list
type_parameters
: attributes? type_parameter
type_parameter
: identifier
antlr
class_base
: ':' class_type
| ':' interface_type_list
interface_type_list
La clase base especificada en una declaración de clase puede ser un tipo de clase
construido (tipos construidos). Una clase base no puede ser un parámetro de tipo por su
cuenta, aunque puede incluir los parámetros de tipo que se encuentran en el ámbito.
C#
Clases base
Cuando se incluye un class_type en el class_base, especifica la clase base directa de la
clase que se está declarando. Si una declaración de clase no tiene class_base, o si el
class_base solo enumera los tipos de interfaz, se supone que la clase base directa es
object . Una clase hereda los miembros de su clase base directa, como se describe en
herencia.
En el ejemplo
C#
class A {}
class B: A {}
C#
La clase base directa de un tipo de clase debe ser al menos igual de accesible que el
propio tipo de clase (dominios de accesibilidad). Por ejemplo, se trata de un error en
tiempo de compilación para public que una clase se derive de una private internal
clase o.
La clase base directa de un tipo de clase no debe ser ninguno de los siguientes tipos:
System.Array , System.Delegate , System.MulticastDelegate , System.Enum o
C#
class A<T> {
public class B {}
class C : A<C.B> {}
Las clases base de un tipo de clase son la clase base directa y sus clases base. En otras
palabras, el conjunto de clases base es el cierre transitivo de la relación de clase base
directa. En el ejemplo anterior, las clases base de B son A y object . En el ejemplo
C#
class A {...}
A excepción de la clase object , cada tipo de clase tiene exactamente una clase base
directa. La object clase no tiene ninguna clase base directa y es la última clase base de
todas las demás clases.
En el ejemplo
C#
class A: A {}
C#
class A: B {}
class B: C {}
class C: A {}
es un error porque las clases dependen circularmente por sí mismas. Por último, el
ejemplo
C#
class A: B.C {}
class B: A
public class C {}
produce un error en tiempo de compilación porque A depende de B.C (su clase base
directa), que depende de B (su clase envolvente inmediata), que depende circularmente
de A .
Tenga en cuenta que una clase no depende de las clases anidadas en ella. En el ejemplo
C#
class A
class B: A {}
inmediata), pero A no depende de B (puesto que no B es una clase base ni una clase
envolvente de A ). Por lo tanto, el ejemplo es válido.
C#
sealed class A {}
Implementaciones de interfaces
Una especificación de class_base puede incluir una lista de tipos de interfaz, en cuyo
caso se dice que la clase implementa directamente los tipos de interfaz especificados.
Las implementaciones de interfaz se tratan en las implementaciones de interfaz.
antlr
type_parameter_constraints_clause
: 'where' type_parameter ':' type_parameter_constraints
type_parameter_constraints
: primary_constraint
| secondary_constraints
| constructor_constraint
primary_constraint
: class_type
| 'class'
| 'struct'
secondary_constraints
: interface_type
| type_parameter
constructor_constraint
palabra clave.
La lista de restricciones dadas en una where cláusula puede incluir cualquiera de los
componentes siguientes, en este orden: una restricción Primary única, una o más
restricciones secundarias y la restricción de constructor, new() .
Una restricción Primary puede ser un tipo de clase o una restricción de tipo de
referencia* class o la restricción de tipo de valor struct . Una restricción secundaria
puede ser una _type_parameter * o interface_type.
Los tipos de puntero nunca pueden ser argumentos de tipo y no se tienen en cuenta
para satisfacer las restricciones de tipo de valor o tipo de referencia.
Un tipo especificado como restricción interface_type debe cumplir las siguientes reglas:
En cualquier caso, la restricción puede incluir cualquiera de los parámetros de tipo del
tipo o la declaración de método asociados como parte de un tipo construido, y puede
implicar el tipo que se declara.
Un tipo especificado como restricción type_parameter debe cumplir las siguientes reglas:
Cualquier restricción debe ser coherente entre los parámetros de tipo dependiente. Si el
parámetro S de tipo depende del parámetro de tipo T , entonces:
Es válido para S que tenga la restricción de tipo de valor y T para que tenga la
restricción de tipo de referencia. De hecho, esto limita T a los tipos System.Object ,, y a
System.ValueType System.Enum cualquier tipo de interfaz.
C#
interface IPrintable
void Print();
interface IComparable<T>
interface IKeyProvider<T>
T GetKey();
class Dictionary<K,V>
where K: IComparable<K>
...
C#
class Circular<S,T>
where S: T
...
C#
class Sealed<S,T>
where S: T
...
class A {...}
class B {...}
class Incompat<S,T>
where S: A, T
...
class StructWithClass<S,T,U>
where S: struct, T
where T: U
...
Estas reglas garantizan que la clase base efectiva siempre sea una class_type.
Los valores de un tipo de parámetro de tipo restringido se pueden utilizar para tener
acceso a los miembros de instancia que implican las restricciones. En el ejemplo
C#
interface IPrintable
void Print();
void PrintOne(T x) {
x.Print();
Cuerpo de clase
El class_body de una clase define los miembros de esa clase.
antlr
class_body
Tipos parciales
Una declaración de tipos se puede dividir en varias declaraciones de tipos parciales. La
declaración de tipos se construye a partir de sus elementos siguiendo las reglas de esta
sección, en la que se trata como una única declaración durante el resto del
procesamiento en tiempo de compilación y en tiempo de ejecución del programa.
Cada parte de una declaración de tipo parcial debe incluir un partial modificador.
Debe tener el mismo nombre y estar declarado en el mismo espacio de nombres o
declaración de tipos que las demás partes. El partial modificador indica que pueden
existir partes adicionales de la declaración de tipos en otro lugar, pero la existencia de
tales partes adicionales no es un requisito; es válido para un tipo con una sola
declaración que incluya el partial modificador.
Todas las partes de un tipo parcial se deben compilar de forma que se puedan combinar
las partes en tiempo de compilación en una única declaración de tipo. Los tipos
parciales no permiten que se extiendan los tipos ya compilados.
Atributos
Los atributos de un tipo parcial se determinan mediante la combinación, en un orden no
especificado, con los atributos de cada uno de los elementos. Si un atributo se coloca en
varias partes, es equivalente a especificar el atributo varias veces en el tipo. Por ejemplo,
las dos partes:
C#
[Attr1, Attr2("hello")]
partial class A {}
[Attr3, Attr2("goodbye")]
partial class A {}
C#
class A {}
Cuando el unsafe modificador se usa en una declaración de tipo parcial, solo esa parte
concreta se considera un contexto no seguro (contextos no seguros).
En el ejemplo
C#
partial class Dictionary<K,V>
where K: IComparable<K>
...
where K: IComparable<K>
...
...
es correcto porque las partes que incluyen restricciones (las dos primeras) especifican de
forma eficaz el mismo conjunto de restricciones principales, secundarias y constructores
para el mismo conjunto de parámetros de tipo, respectivamente.
Clase base
Cuando una declaración de clase parcial incluye una especificación de clase base, debe
coincidir con todas las demás partes que incluyen una especificación de clase base. Si
ninguna parte de una clase parcial incluye una especificación de clase base, la clase base
se convierte en System.Object (clases base).
Interfaces base
El conjunto de interfaces base para un tipo declarado en varias partes es la Unión de las
interfaces base especificadas en cada parte. Solo se puede asignar un nombre a una
interfaz base determinada una vez en cada parte, pero se permite que varias partes
denominen a las mismas interfaces base. Solo debe haber una implementación de los
miembros de una interfaz base determinada.
En el ejemplo
C#
C#
partial class X
...
Miembros
A excepción de los métodos parciales (métodos parciales), el conjunto de miembros de
un tipo declarado en varias partes es simplemente la Unión del conjunto de miembros
declarado en cada parte. Los cuerpos de todas las partes de la declaración de tipos
comparten el mismo espacio de declaración (declaraciones), y el ámbito de cada
miembro (ámbitos) se extiende a los cuerpos de todas las partes. El dominio de
accesibilidad de cualquier miembro siempre incluye todas las partes del tipo envolvente;
un private miembro declarado en una parte está disponible libremente desde otra
parte. Es un error en tiempo de compilación declarar el mismo miembro en más de una
parte del tipo, a menos que ese miembro sea un tipo con el partial modificador.
C#
partial class A
int y;
partial class A
int z;
El orden de los miembros dentro de un tipo rara vez es significativo para el código de
C#, pero puede ser importante al interactuar con otros lenguajes y entornos. En estos
casos, el orden de los miembros dentro de un tipo declarado en varias partes es
indefinido.
Métodos parciales
Los métodos parciales se pueden definir en una parte de una declaración de tipos e
implementarse en otro. La implementación es opcional. Si ninguna parte implementa el
método parcial, la declaración de método parcial y todas las llamadas a ella se quitan de
la declaración de tipos resultante de la combinación de los elementos.
Los métodos parciales son útiles para permitir que una parte de una declaración de
tipos Personalice el comportamiento de otra parte, por ejemplo, una generada por una
herramienta. Considere la siguiente declaración de clase parcial:
C#
string name;
set {
OnNameChanging(value);
name = value;
OnNameChanged();
Si esta clase se compila sin ningún otro elemento, se quitarán las declaraciones de
método parcial de definición y sus invocaciones, y la declaración de clase combinada
resultante será equivalente a la siguiente:
C#
class Customer
string name;
C#
C#
class Customer
string name;
set {
OnNameChanging(value);
name = value;
OnNameChanged();
void OnNameChanged()
Enlace de nombre
Aunque cada parte de un tipo extensible se debe declarar dentro del mismo espacio de
nombres, las partes se escriben normalmente en diferentes declaraciones de espacio de
nombres. Por lo tanto, using pueden estar presentes directivas diferentes (mediante
directivas) para cada parte. Al interpretar nombres simples (inferencia de tipos) dentro
de una parte, solo using se tienen en cuenta las directivas de las declaraciones de
espacio de nombres que la forman. Esto puede dar lugar a que el mismo identificador
tenga significados diferentes en distintas partes:
C#
namespace N
partial class A
namespace N
partial class A
Miembros de clase
Los miembros de una clase se componen de los miembros introducidos por su
class_member_declaration s y los miembros heredados de la clase base directa.
antlr
class_member_declaration
: constant_declaration
| field_declaration
| method_declaration
| property_declaration
| event_declaration
| indexer_declaration
| operator_declaration
| constructor_declaration
| destructor_declaration
| static_constructor_declaration
| type_declaration
Los miembros heredados de un tipo de clase (herencia) no forman parte del espacio de
declaración de una clase. Por lo tanto, una clase derivada puede declarar un miembro
con el mismo nombre o signatura que un miembro heredado (que en efecto oculta el
miembro heredado).
El tipo de instancia
Cada declaración de clase tiene un tipo enlazado asociado (tipos enlazados y sin
enlazar), el tipo de instancia. En el caso de una declaración de clase genérica, el tipo de
instancia se forma creando un tipo construido (tipos construidos) a partir de la
declaración de tipos, y cada uno de los argumentos de tipo proporcionados es el
parámetro de tipo correspondiente. Dado que el tipo de instancia utiliza los parámetros
de tipo, solo se puede usar cuando los parámetros de tipo están en el ámbito; es decir,
dentro de la declaración de clase. El tipo de instancia es el tipo de this para el código
escrito dentro de la declaración de clase. En el caso de las clases no genéricas, el tipo de
instancia es simplemente la clase declarada. A continuación se muestran varias
declaraciones de clase junto con sus tipos de instancia:
C#
C#
class Gen<T,U>
public T[,] a;
C#
public int[,][] a;
Todos los miembros de una clase genérica pueden usar parámetros de tipo de cualquier
clase envolvente, ya sea directamente o como parte de un tipo construido. Cuando se
usa un tipo construido cerrado determinado (tipos abiertos y cerrados) en tiempo de
ejecución, cada uso de un parámetro de tipo se reemplaza por el argumento de tipo
real proporcionado al tipo construido. Por ejemplo:
C#
class C<V>
public V f1;
public C(V x) {
this.f1 = x;
this.f2 = this;
}
class Application
Console.WriteLine(x1.f1); // Prints 1
Herencia
Una clase hereda los miembros de su tipo de clase base directa. La herencia significa
que una clase contiene implícitamente todos los miembros de su tipo de clase base
directa, a excepción de los constructores de instancias, destructores y constructores
estáticos de la clase base. Algunos aspectos importantes de la herencia son:
El miembro heredado de un tipo de clase construido son los miembros del tipo de clase
base inmediato (clases base), que se encuentra sustituyendo los argumentos de tipo del
tipo construido por cada aparición de los parámetros de tipo correspondientes en la
especificación class_base . Estos miembros, a su vez, se transforman sustituyendo, por
cada type_parameter en la declaración de miembro, el type_argument correspondiente
de la especificación de class_base .
C#
class B<U>
El nuevo modificador
Un class_member_declaration puede declarar un miembro con el mismo nombre o
signatura que un miembro heredado. Cuando esto ocurre, se dice que el miembro de la
clase derivada oculta el miembro de la clase base. Ocultar un miembro heredado no se
considera un error, pero hace que el compilador emita una advertencia. Para suprimir la
advertencia, la declaración del miembro de la clase derivada puede incluir un new
modificador para indicar que el miembro derivado está pensado para ocultar el
miembro base. Este tema se describe con más detalle en ocultarse a travésde la
herencia.
Modificadores de acceso
Un class_member_declaration puede tener cualquiera de los cinco tipos posibles de
accesibilidad declarada (accesibilidad declarada): public , protected internal ,
protected , internal o private . A excepción de la protected internal combinación,
Tipos constituyentes
Los tipos que se usan en la declaración de un miembro se denominan tipos
constituyentes de ese miembro. Los tipos constituyentes posibles son el tipo de una
constante, un campo, una propiedad, un evento o un indizador, el tipo de valor devuelto
de un método o un operador, y los tipos de parámetro de un método, indizador,
operador o constructor de instancia. Los tipos constituyentes de un miembro deben ser
al menos tan accesibles como el propio miembro (restricciones de accesibilidad).
En el ejemplo siguiente se muestran las reglas para tener acceso a los miembros
estáticos y de instancia:
C#
class Test
int x;
static int y;
void F() {
t.x = 1; // Ok
Test.y = 1; // Ok
Tipos anidados
Un tipo declarado dentro de una declaración de clase o struct se denomina *tipo
anidado _. Un tipo que se declara dentro de una unidad de compilación o espacio de
nombres se denomina un tipo no anidado* *.
En el ejemplo
C#
using System;
class A
class B
Console.WriteLine("A.B.F");
Nombre completo
pueden tener estas formas de accesibilidad declarada, además de una o varias formas
adicionales de accesibilidad declarada, dependiendo de si el tipo contenedor es una
clase o un struct:
Un tipo anidado que se declara en una clase puede tener cualquiera de las cinco
formas de accesibilidad declarada ( public , protected internal , protected ,
internal o private ) y, al igual que otros miembros de clase, tiene como valor
predeterminado la private accesibilidad declarada.
Un tipo anidado que se declara en un struct puede tener cualquiera de las tres
formas de accesibilidad declarada ( public , internal o private ) y, al igual que
otros miembros de struct, tiene como valor predeterminado la private
accesibilidad declarada.
En el ejemplo
C#
this.Data = data;
this.Next = next;
// Public interface
C#
using System;
class Base
Console.WriteLine("Base.M");
Console.WriteLine("Derived.M.F");
class Test
Derived.M.F();
este acceso
Un tipo anidado y su tipo contenedor no tienen una relación especial con respecto a
this_access (este acceso). En concreto, this dentro de un tipo anidado no se puede usar
para hacer referencia a los miembros de instancia del tipo contenedor. En los casos en
los que un tipo anidado necesite tener acceso a los miembros de instancia de su tipo
contenedor, se puede proporcionar acceso proporcionando this para la instancia del
tipo contenedor como argumento de constructor para el tipo anidado. El ejemplo
siguiente
C#
using System;
class C
int i = 123;
n.G();
C this_c;
public Nested(C c) {
this_c = c;
Console.WriteLine(this_c.i);
class Test
C c = new C();
c.F();
muestra esta técnica. Una instancia de C crea una instancia de Nested y pasa su propia
this al Nested constructor de para proporcionar el acceso posterior a C los miembros
de instancia de.
Un tipo anidado tiene acceso a todos los miembros a los que se puede tener acceso a
su tipo contenedor, incluidos los miembros del tipo contenedor que tienen private y
protected declaran la accesibilidad. En el ejemplo
C#
using System;
class C
Console.WriteLine("C.F");
}
F();
class Test
C.Nested.G();
muestra una clase C que contiene una clase anidada Nested . Dentro de Nested , el
método G llama al método estático F definido en C y F tiene una accesibilidad
declarada privada.
Un tipo anidado también puede tener acceso a los miembros protegidos definidos en
un tipo base de su tipo contenedor. En el ejemplo
C#
using System;
class Base
Console.WriteLine("Base.F");
d.F(); // ok
class Test
n.G();
Una declaración de clase genérica puede contener declaraciones de tipos anidados. Los
parámetros de tipo de la clase envolvente se pueden usar dentro de los tipos anidados.
Una declaración de tipos anidados puede contener parámetros de tipo adicionales que
solo se aplican al tipo anidado.
C#
class Outer<T>
class Inner<U>
C#
class Outer<T>
C#
T get_P();
Ambas firmas están reservadas, aunque la propiedad sea de solo lectura o de solo
escritura.
En el ejemplo
C#
using System;
class A
public int P {
class B: A
return 456;
class Test
B b = new B();
A a = b;
Console.WriteLine(a.P);
Console.WriteLine(b.P);
Console.WriteLine(b.get_P());
una clase A define una propiedad de solo lectura P , de modo que se reservan firmas
para get_P set_P los métodos y. Una clase se B deriva de A y oculta ambas firmas
reservadas. En el ejemplo se genera el resultado:
Consola
123
123
456
C#
C#
T get_Item(L);
Ambas firmas están reservadas, aunque el indizador sea de solo lectura o de solo
escritura.
C#
void Finalize();
Constantes
*Constant _ es un miembro de clase que representa un valor constante: un valor que se
puede calcular en tiempo de compilación. Un _constant_declaration * introduce una o
más constantes de un tipo determinado.
antlr
constant_declaration
constant_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
constant_declarators
constant_declarator
El tipo especificado en una declaración de constante debe ser sbyte , byte , short ,
ushort , int , uint , long , ulong , char , float , double , decimal , bool , string , un
El tipo de una constante debe ser al menos tan accesible como la propia constante
(restricciones de accesibilidad).
El valor de una constante se obtiene en una expresión usando un simple_name
(nombres simples) o un member_access (acceso a miembros).
Cuando se desea un nombre simbólico para un valor constante, pero cuando el tipo de
ese valor no se permite en una declaración de constante, o cuando el valor no se puede
calcular en tiempo de compilación mediante un constant_expression, readonly se puede
usar en su lugar un campo (campos de solo lectura).
C#
class A
es equivalente a
C#
class A
C#
class A
class B
en primer lugar, el compilador evalúa A.Y , evalúa B.Z y, por último, evalúa y A.X
genera los valores 10 , 11 y 12 . Las declaraciones de constantes pueden depender de
constantes de otros programas, pero dichas dependencias solo son posibles en una
dirección. Tomando como referencia el ejemplo anterior, si A y B se declararon en
programas independientes, sería posible A.X que dependa de B.Z , pero B.Z no puede
depender simultáneamente A.Y .
Campos
Un *campo _ es un miembro que representa una variable asociada con un objeto o una
clase. Un _field_declaration * introduce uno o más campos de un tipo determinado.
antlr
field_declaration
field_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'readonly'
| 'volatile'
| field_modifier_unsafe
variable_declarators
variable_declarator
variable_initializer
: expression
| array_initializer
El tipo de un campo debe ser al menos igual de accesible que el propio campo
(restricciones de accesibilidad).
C#
class A
es equivalente a
C#
class A
Por ejemplo:
C#
class C<V>
public C() {
count++;
class Application
Console.WriteLine(C<int>.Count); // Prints 1
Console.WriteLine(C<int>.Count); // Prints 1
Console.WriteLine(C<int>.Count); // Prints 2
Un campo de instancia pertenece a una instancia de. En concreto, cada instancia de una
clase contiene un conjunto independiente de todos los campos de instancia de esa
clase.
Las diferencias entre los miembros estáticos y de instancia se tratan más adelante en
miembros estáticos y de instancia.
C#
red = r;
green = g;
blue = b;
los Black miembros,, White Red , Green y Blue no se pueden declarar como const
miembros porque sus valores no se pueden calcular en tiempo de compilación. Sin
embargo, si se declaran static readonly en su lugar, se produce el mismo efecto.
Las constantes y los campos de solo lectura tienen una semántica de versiones binaria
diferente. Cuando una expresión hace referencia a una constante, el valor de la
constante se obtiene en tiempo de compilación, pero cuando una expresión hace
referencia a un campo de solo lectura, el valor del campo no se obtiene hasta el tiempo
de ejecución. Considere una aplicación que consta de dos programas independientes:
C#
using System;
namespace Program1
namespace Program2
class Test
Console.WriteLine(Program1.Utils.X);
Los Program1 Program2 espacios de nombres y denotan dos programas que se compilan
por separado. Dado Program1.Utils.X que se declara como un campo estático de solo
lectura, la salida del valor de la Console.WriteLine instrucción no se conoce en tiempo
de compilación, sino que se obtiene en tiempo de ejecución. Por lo tanto, si el valor de
X se cambia y Program1 se vuelve a compilar, la Console.WriteLine instrucción generará
el nuevo valor aunque no se vuelva a Program2 compilar. Sin embargo, había X sido una
constante, el valor de X se habría obtenido en el momento en Program2 que se compiló
y no se vería afectado por los cambios en Program1 hasta que se vuelva a Program2
compilar.
Campos volátiles
Cuando una field_declaration incluye un volatile modificador, los campos introducidos
por esa declaración son campos volátiles.
En el caso de los campos no volátiles, las técnicas de optimización que reordenan las
instrucciones pueden provocar resultados inesperados e imprevisibles en programas
multiproceso que tienen acceso a campos sin sincronización, como los que proporciona
el lock_statement (la instrucción lock). Estas optimizaciones las puede realizar el
compilador, el sistema en tiempo de ejecución o el hardware. En el caso de los campos
volátiles, las optimizaciones de reordenación están restringidas:
Una lectura de un campo volátil se denomina lectura volátil. Una lectura volátil
tiene la "semántica de adquisición"; es decir, se garantiza que se produce antes de
las referencias a la memoria que se producen después de ella en la secuencia de
instrucciones.
La escritura de un campo volátil se denomina escritura volátil. Una escritura volátil
tiene "semántica de versión"; es decir, se garantiza que se produce después de
cualquier referencia de memoria antes de la instrucción de escritura en la
secuencia de instrucciones.
Estas restricciones aseguran que todos los subprocesos observarán las operaciones de
escritura volátiles realizadas por cualquier otro subproceso en el orden en que se
realizaron. No se requiere una implementación compatible para proporcionar una
ordenación total única de escrituras volátiles, como se aprecia en todos los subprocesos
de ejecución. El tipo de un campo volátil debe ser uno de los siguientes:
Reference_type.
Tipo byte , sbyte , short , ushort , int , uint , char ,,, float bool
System.IntPtr o System.UIntPtr .
Enum_type que tiene un tipo base enum de byte , sbyte , short , ushort , int o
uint .
En el ejemplo
C#
using System;
using System.Threading;
class Test
result = 143;
finished = true;
finished = false;
// finished to true.
for (;;) {
if (finished) {
return;
genera el resultado:
Consola
result = 143
En este ejemplo, el método Main inicia un nuevo subproceso que ejecuta el método
Thread2 . Este método almacena un valor en un campo no volátil denominado result y,
leer el valor 143 del campo result . Si finished no se ha declarado el campo volatile
, se permite que el almacén result sea visible para el subproceso principal después del
almacén en finished y, por lo tanto, para que el subproceso principal Lea el valor 0 del
campo result . Declarar finished como un volatile campo evita cualquier
incoherencia.
Inicialización de campos
El valor inicial de un campo, ya sea un campo estático o un campo de instancia, es el
valor predeterminado (valores predeterminados) del tipo de campo. No es posible
observar el valor de un campo antes de que se haya producido esta inicialización
predeterminada y, por tanto, un campo nunca es "no inicializado". En el ejemplo
C#
using System;
class Test
static bool b;
int i;
genera el resultado
Consola
b = False, i = 0
En el ejemplo
C#
using System;
class Test
int i = 100;
string s = "Hello";
genera el resultado
Consola
Dado que una asignación x tiene lugar cuando los inicializadores de campo estáticos
ejecutan y se asignan a i y s se producen cuando se ejecutan los inicializadores de
campo de instancia.
C#
using System;
class Test
static int a = b + 1;
static int b = a + 1;
Consola
a = 1, b = 2
Dado que los campos estáticos a y b se inicializan en 0 (el valor predeterminado para
int ) antes de que se ejecuten sus inicializadores. Cuando se ejecuta el inicializador de
C#
using System;
class Test
Console.WriteLine(s);
return 1;
class A
class B
Consola
Init A
Init B
1 1
o la salida:
Consola
Init B
Init A
1 1
C#
using System;
class Test
Console.WriteLine(s);
return 1;
class A
static A() {}
class B
static B() {}
Consola
Init B
Init A
1 1
Dado que las reglas para cuando los constructores estáticos se ejecutan (como se
definen en constructores estáticos), proporcionan el B constructor estático de (y, por
tanto, los B inicializadores de campo estáticos) que se deben ejecutar antes que los A
inicializadores de campo y constructores estáticos de.
C#
class A
int x = 1;
Métodos
Un *método _ es un miembro que implementa un cálculo o una acción que puede
realizar un objeto o una clase. Los métodos se declaran mediante _method_declaration *
s:
antlr
method_declaration
: method_header method_body
method_header
method_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'virtual'
| 'sealed'
| 'override'
| 'abstract'
| 'extern'
| 'async'
| method_modifier_unsafe
return_type
: type
| 'void'
member_name
: identifier
method_body
: block
| ';'
Una declaración tiene una combinación válida de modificadores si se cumplen todas las
condiciones siguientes:
Un método que tiene el async modificador es una función asincrónica y sigue las reglas
descritas en funciones asincrónicas.
El return_type de una declaración de método especifica el tipo del valor calculado y
devuelto por el método. El return_type es void si el método no devuelve un valor. Si la
Declaración incluye el partial modificador, el tipo de valor devuelto debe ser void .
El member_name especifica el nombre del método. A menos que el método sea una
implementación explícita de un miembro de interfaz (implementaciones explícitas de
miembros de interfaz), el member_name es simplemente un identificador. En el caso de
una implementación explícita de un miembro de interfaz, el member_name se compone
de un interface_type seguido de un " . " y un identificador.
El nombre de un método debe ser distinto de los nombres de todos los demás métodos
que no se declaran en la misma clase. Además, la firma de un método debe ser
diferente de las firmas de todos los demás métodos declarados en la misma clase y dos
métodos declarados en la misma clase no pueden tener firmas que solo difieran en ref
y out .
Todos los parámetros formales y los parámetros de tipo deben tener nombres
diferentes.
Parámetros de método
Los parámetros de un método, si los hay, se declaran mediante el formal_parameter_list
del método.
antlr
formal_parameter_list
: fixed_parameters
| parameter_array
fixed_parameters
fixed_parameter
default_argument
: '=' expression
parameter_modifier
: 'ref'
| 'out'
| 'this'
parameter_array
La lista de parámetros formales está formada por uno o varios parámetros separados
por comas, de los cuales solo el último puede ser un parameter_array.
a constant_expression
una expresión con el formato, new S() donde S es un tipo de valor.
una expresión con el formato, default(S) donde S es un tipo de valor.
C#
public void M(
ref int i,
decimal d,
bool b = false,
bool? n = false,
string s = "Hello",
object o = null,
T t = default(T),
params int[] a
) { }
Como se describe en firmas y sobrecarga, los ref out modificadores y forman parte de
la firma de un método, pero el params modificador no es.
Parámetros de valor
Un parámetro declarado sin modificadores es un parámetro de valor. Un parámetro de
valor corresponde a una variable local que obtiene su valor inicial del argumento
correspondiente proporcionado en la invocación del método.
Parámetros de referencia
Un parámetro declarado con un ref modificador es un parámetro de referencia. A
diferencia de un parámetro de valor, un parámetro de referencia no crea una nueva
ubicación de almacenamiento. En su lugar, un parámetro de referencia representa la
misma ubicación de almacenamiento que la variable proporcionada como argumento
en la invocación del método.
C#
using System;
class Test
int temp = x;
x = y;
y = temp;
int i = 1, j = 2;
genera el resultado
Consola
i = 2, j = 1
C#
class A
string s;
s = "One";
a = "Two";
b = "Three";
void G() {
Parámetros de salida
Un parámetro declarado con un out modificador es un parámetro de salida. De forma
similar a un parámetro de referencia, un parámetro de salida no crea una nueva
ubicación de almacenamiento. En su lugar, un parámetro de salida representa la misma
ubicación de almacenamiento que la variable proporcionada como argumento en la
invocación del método.
Los parámetros de salida se usan normalmente en métodos que producen varios valores
devueltos. Por ejemplo:
C#
using System;
class Test
static void SplitPath(string path, out string dir, out string name) {
int i = path.Length;
while (i > 0) {
char ch = path[i - 1];
i--;
name = path.Substring(i);
}
Console.WriteLine(dir);
Console.WriteLine(name);
Consola
c:\Windows\System\
hello.txt
Tenga en cuenta que las dir name variables y se pueden desasignar antes de que se
pasen a SplitPath , y que se consideran definitivamente asignadas después de la
llamada.
Matrices de parámetros
Una matriz de parámetros permite especificar argumentos de una de estas dos maneras
en una invocación de método:
El argumento dado para una matriz de parámetros puede ser una expresión única
que se pueda convertir implícitamente (conversiones implícitas) en el tipo de
matriz de parámetros. En este caso, la matriz de parámetros actúa exactamente
como un parámetro de valor.
Como alternativa, la invocación puede especificar cero o más argumentos para la
matriz de parámetros, donde cada argumento es una expresión que se puede
convertir implícitamente (conversiones implícitas) en el tipo de elemento de la
matriz de parámetros. En este caso, la invocación crea una instancia del tipo de
matriz de parámetros con una longitud correspondiente al número de
argumentos, inicializa los elementos de la instancia de la matriz con los valores de
argumento especificados y utiliza la instancia de matriz recién creada como
argumento real.
En el ejemplo
C#
using System;
class Test
Console.WriteLine();
F(arr);
F();
genera el resultado
Consola
En el ejemplo
C#
using System;
class Test
Console.WriteLine("F(object[])");
Console.WriteLine("F()");
}
Console.WriteLine("F(object,object)");
F();
F(1);
F(1, 2);
F(1, 2, 3);
F(1, 2, 3, 4);
genera el resultado
Consola
F();
F(object[]);
F(object,object);
F(object[]);
F(object[]);
En el ejemplo, dos de las formas expandidas posibles del método con una matriz de
parámetros ya están incluidas en la clase como métodos normales. Por lo tanto, estos
formularios expandidos no se tienen en cuenta al realizar la resolución de sobrecarga, y
las invocaciones del método primero y tercer, por tanto, seleccionan los métodos
normales. Cuando una clase declara un método con una matriz de parámetros, no es
raro incluir también algunos de los formularios expandidos como métodos normales. Al
hacerlo, es posible evitar la asignación de una instancia de matriz que se produce
cuando se invoca una forma expandida de un método con una matriz de parámetros.
En el ejemplo
C#
using System;
class Test
Console.Write(o.GetType().FullName);
Console.Write(" ");
Console.WriteLine();
object o = a;
F(a);
F((object)a);
F(o);
F((object[])o);
}
genera el resultado
Consola
System.Object[]
System.Object[]
Las diferencias entre los miembros estáticos y de instancia se tratan más adelante en
miembros estáticos y de instancia.
Métodos virtuales
Cuando una declaración de método de instancia incluye un virtual modificador, se
dice que se trata de un método virtual. Cuando no virtual existe ningún modificador,
se dice que el método es un método no virtual.
La implementación de un método no virtual es invariable: la implementación es la
misma si se invoca el método en una instancia de la clase en la que se declara o una
instancia de una clase derivada. Por el contrario, las clases derivadas pueden reemplazar
la implementación de un método virtual. El proceso de reemplazar la implementación
de un método virtual heredado se conoce como invalidar ese método (métodos de
invalidación).
Para cada método virtual declarado en o heredado por una clase, existe una
implementación más derivada del método con respecto a esa clase. La implementación
más derivada de un método virtual M con respecto a una clase R se determina de la
manera siguiente:
En el ejemplo siguiente se muestran las diferencias entre los métodos virtuales y los no
virtuales:
C#
using System;
class A
class B: A
class Test
B b = new B();
A a = b;
a.F();
b.F();
a.G();
b.G();
Consola
A.F
B.F
B.G
B.G
Observe que la instrucción a.G() invoca B.G , no A.G . Esto se debe a que el tipo en
tiempo de ejecución de la instancia (que es B ), y no el tipo en tiempo de compilación
de la instancia (que es A ), determina la implementación de método real que se va a
invocar.
Dado que los métodos pueden ocultar métodos heredados, es posible que una clase
contenga varios métodos virtuales con la misma firma. Esto no presenta un problema de
ambigüedad, ya que todo menos el método más derivado está oculto. En el ejemplo
C#
using System;
class A
class B: A
class C: B
class D: C
class Test
D d = new D();
A a = d;
B b = d;
C c = d;
a.F();
b.F();
c.F();
d.F();
las C D clases y contienen dos métodos virtuales con la misma firma: los introducidos
por A y los introducidos por C . El método introducido por C oculta el método
heredado de A . Por lo tanto, la declaración de invalidación en D invalida el método
introducido por C , y no es posible D que invalide el método introducido por A . En el
ejemplo se genera el resultado:
Consola
B.F
B.F
D.F
D.F
Tenga en cuenta que es posible invocar el método virtual oculto mediante el acceso a
una instancia de D a través de un tipo menos derivado en el que el método no está
oculto.
Métodos de invalidación
Cuando una declaración de método de instancia incluye un override modificador, se
dice que el método es un método de invalidación. Un método de invalidación invalida
un método virtual heredado con la misma firma. Mientras que una declaración de
método virtual introduce un método nuevo, una declaración de método de reemplazo
especializa un método virtual heredado existente proporcionando una nueva
implementación de ese método.
El método invalidado por una override declaración se conoce como el método base
invalidado. Para un método de invalidación M declarado en una clase C , el método
base invalidado se determina examinando cada tipo de clase base de C , empezando
por el tipo de clase base directa de C y continuando con cada tipo de clase base directo
sucesivo, hasta que en un tipo de clase base determinado se encuentra un método
accesible que tiene la misma firma que M después de la sustitución de los argumentos
de tipo. Con el fin de localizar el método base invalidado, se considera que un método
es accesible si es, si es, si es, public protected protected internal o si es y se internal
declara en el mismo programa que C .
En el ejemplo siguiente se muestra cómo funcionan las reglas de reemplazo para las
clases genéricas:
C#
class D: C<string>
C#
class A
int x;
class B: A
int y;
base.PrintFields();
C#
class A
class B: A
En el ejemplo
C#
class A
class B: A
class C: B
Métodos sellados
Cuando una declaración de método de instancia incluye un sealed modificador, se dice
que se trata de un método sellado. Si una declaración de método de instancia incluye el
sealed modificador, también debe incluir el override modificador. El uso del sealed
modificador impide que una clase derivada Reemplace el método.
En el ejemplo
C#
using System;
class A
Console.WriteLine("A.F");
}
Console.WriteLine("A.G");
}
class B: A
Console.WriteLine("B.F");
}
Console.WriteLine("B.G");
}
class C: B
Console.WriteLine("C.G");
}
Métodos abstractos
Cuando una declaración de método de instancia incluye un abstract modificador, se
dice que el método es un método abstracto. Aunque un método abstracto también es
implícitamente un método virtual, no puede tener el modificador virtual .
En el ejemplo
C#
g.DrawEllipse(r);
g.DrawRect(r);
la Shape clase define la noción abstracta de un objeto de forma geométrica que puede
dibujarse a sí mismo. El Paint método es abstracto porque no hay ninguna
implementación predeterminada significativa. Las Ellipse Box clases y son Shape
implementaciones concretas. Dado que estas clases no son abstractas, son necesarias
para invalidar el Paint método y proporcionar una implementación real.
C#
abstract class A
class B: A
Se permite que una declaración de método abstracto invalide un método virtual. Esto
permite a una clase abstracta forzar la reimplementación del método en las clases
derivadas y hace que la implementación original del método no esté disponible. En el
ejemplo
C#
using System;
class A
Console.WriteLine("A.F");
}
abstract class B: A
class C: B
Console.WriteLine("C.F");
}
A la clase declara un método virtual, B la clase invalida este método con un método
Métodos externos
Cuando una declaración de método incluye un extern modificador, se dice que el
método es un *método externo _. Los métodos externos se implementan externamente,
normalmente con un lenguaje distinto de C#. Dado que una declaración de método
externo no proporciona ninguna implementación real, el _method_body * de un método
externo consiste simplemente en un punto y coma. Es posible que un método externo
no sea genérico.
C#
using System.Text;
using System.Security.Permissions;
using System.Runtime.InteropServices;
class Path
[DllImport("kernel32", SetLastError=true)]
[DllImport("kernel32", SetLastError=true)]
[DllImport("kernel32", SetLastError=true)]
[DllImport("kernel32", SetLastError=true)]
Métodos de extensión
Cuando el primer parámetro de un método incluye el this modificador, se dice que se
trata de un método de extensión. Los métodos de extensión solo se pueden declarar en
clases estáticas no anidadas no genéricas. El primer parámetro de un método de
extensión no puede tener modificadores distintos de this y el tipo de parámetro no
puede ser un tipo de puntero.
El siguiente es un ejemplo de una clase estática que declara dos métodos de extensión:
C#
return Int32.Parse(s);
public static T[] Slice<T>(this T[] source, int index, int count) {
return result;
C#
static class Program
Console.WriteLine(s.ToInt32());
C#
Console.WriteLine(Extensions.ToInt32(s));
Cuando un método tiene un void tipo de resultado y un cuerpo de bloque, las return
instrucciones (la instrucción return) del bloque no pueden especificar una expresión. Si
la ejecución del bloque de un método void se completa normalmente (es decir, el
control fluye fuera del final del cuerpo del método), ese método simplemente vuelve a
su llamador actual.
Cuando un método tiene un void resultado y un cuerpo de expresión, la expresión E
debe ser una statement_expression y el cuerpo es exactamente equivalente a un cuerpo
de bloque del formulario { E; } .
En el ejemplo
C#
class A
return 1;
if (b) {
return 1;
else {
return 0;
Propiedades
Una *propiedad _ es un miembro que proporciona acceso a una característica de un
objeto o una clase. Entre los ejemplos de propiedades se incluyen la longitud de una
cadena, el tamaño de una fuente, el título de una ventana, el nombre de un cliente, etc.
Las propiedades son una extensión natural de los campos: ambos son miembros con
nombre con tipos asociados y la sintaxis para tener acceso a campos y propiedades es la
misma. Sin embargo, a diferencia de los campos, las propiedades no denotan
ubicaciones de almacenamiento. En su lugar, las propiedades tienen _ descriptores de
acceso* que especifican las instrucciones que se ejecutarán cuando se lean o escriban
sus valores. Por tanto, las propiedades proporcionan un mecanismo para asociar
acciones con la lectura y escritura de los atributos de un objeto; Además, permiten
calcular tales atributos.
antlr
property_declaration
property_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'virtual'
| 'sealed'
| 'override'
| 'abstract'
| 'extern'
| property_modifier_unsafe
property_body
property_initializer
Las declaraciones de propiedad están sujetas a las mismas reglas que las declaraciones
de método (métodos) con respecto a las combinaciones válidas de modificadores.
El tipo de una propiedad debe ser al menos igual de accesible que la propiedad en sí
(restricciones de accesibilidad).
Aunque la sintaxis para tener acceso a una propiedad es la misma que la de un campo,
una propiedad no se clasifica como una variable. Por lo tanto, no es posible pasar una
propiedad como ref argumento o out .
Cuando una declaración de propiedad incluye un extern modificador, se dice que la
propiedad es una *propiedad externa _. Dado que una declaración de propiedad
externa no proporciona ninguna implementación real, cada una de sus
_accessor_declarations * consta de un punto y coma.
Una propiedad de instancia está asociada a una instancia determinada de una clase y se
puede tener acceso a esa instancia como this (este acceso) en los descriptores de
acceso de esa propiedad.
Las diferencias entre los miembros estáticos y de instancia se tratan más adelante en
miembros estáticos y de instancia.
Descriptores de acceso
El accessor_declarations de una propiedad especifica las instrucciones ejecutables
asociadas a la lectura y escritura de esa propiedad.
antlr
accessor_declarations
: get_accessor_declaration set_accessor_declaration?
| set_accessor_declaration get_accessor_declaration?
get_accessor_declaration
set_accessor_declaration
accessor_modifier
: 'protected'
| 'internal'
| 'private'
| 'protected' 'internal'
| 'internal' 'protected'
accessor_body
: block
| ';'
Se dice que una propiedad que incluye tanto un descriptor de get acceso como
un set descriptor de acceso es una propiedad de lectura y escritura .
Se dice que una propiedad que solo tiene un get descriptor de acceso es una
propiedad de solo lectura . Es un error en tiempo de compilación que una
propiedad de solo lectura sea el destino de una asignación.
Se dice que una propiedad que solo tiene un set descriptor de acceso es una
propiedad de solo escritura . Excepto como destino de una asignación, se trata de
un error en tiempo de compilación para hacer referencia a una propiedad de solo
escritura en una expresión.
En el ejemplo
C#
get {
return caption;
set {
if (caption != value) {
caption = value;
Repaint();
el Button control declara una propiedad pública Caption . El get descriptor de acceso
de la Caption propiedad devuelve la cadena almacenada en el caption campo privado.
El set descriptor de acceso comprueba si el nuevo valor es diferente del valor actual y,
en ese caso, almacena el nuevo valor y vuelve a dibujar el control. Las propiedades
suelen seguir el patrón mostrado anteriormente: el get descriptor de acceso devuelve
simplemente un valor almacenado en un campo privado y el set descriptor de acceso
modifica ese campo privado y, a continuación, realiza las acciones adicionales necesarias
para actualizar completamente el estado del objeto.
C#
Button okButton = new Button();
Los get set descriptores de acceso y de una propiedad no son miembros distintos y no
es posible declarar los descriptores de acceso de una propiedad por separado. Como
tal, no es posible que los dos descriptores de acceso de una propiedad de lectura y
escritura tengan una accesibilidad diferente. En el ejemplo
C#
class A
Cuando una clase derivada declara una propiedad con el mismo nombre que una
propiedad heredada, la propiedad derivada oculta la propiedad heredada con respecto
a la lectura y la escritura. En el ejemplo
C#
class A
public int P {
set {...}
class B: A
get {...}
C#
B b = new B();
C#
class Label
private int x, y;
this.x = x;
this.y = y;
this.caption = caption;
public int X {
get { return x; }
public int Y {
get { return y; }
En este caso, la Label clase usa dos int campos, x y y , para almacenar su ubicación.
La ubicación se expone públicamente como X y una Y propiedad y como Location
propiedad de tipo Point . Si, en una versión futura de Label , resulta más cómodo
almacenar la ubicación Point internamente, el cambio se puede realizar sin que afecte a
la interfaz pública de la clase:
C#
class Label
this.caption = caption;
public int X {
public int Y {
x y Los campos tenían y en su lugar public readonly , habría sido imposible realizar
este cambio en la Label clase.
C#
class Counter
C#
using System.IO;
get {
if (reader == null) {
return reader;
get {
if (writer == null) {
return writer;
get {
if (error == null) {
return error;
La Console clase contiene tres propiedades, In , Out y Error , que representan los
dispositivos de entrada, salida y error estándar, respectivamente. Al exponer estos
miembros como propiedades, la Console clase puede retrasar su inicialización hasta que
se usen realmente. Por ejemplo, al hacer referencia por primera vez a la Out propiedad,
como en
C#
Console.Out.WriteLine("hello, world");
En el ejemplo siguiente:
C#
C#
En el ejemplo siguiente:
C#
C#
Observe que las asignaciones al campo de solo lectura son válidas, ya que se producen
dentro del constructor.
Accesibilidad
Si un descriptor de acceso tiene un accessor_modifier, el dominio de accesibilidad
(dominios de accesibilidad) del descriptor de acceso se determina mediante la
accesibilidad declarada de la accessor_modifier. Si un descriptor de acceso no tiene un
accessor_modifier, el dominio de accesibilidad del descriptor de acceso se determina a
partir de la accesibilidad declarada de la propiedad o del indizador.
propiedad accesible.
C#
class A
set { }
get { return 5; }
set { }
class B: A
class M
B b = new B();
Un descriptor de acceso que se utiliza para implementar una interfaz no puede tener un
accessor_modifier. Si solo se usa un descriptor de acceso para implementar una interfaz,
el otro descriptor de acceso se puede declarar con un accessor_modifier:
C#
public interface I
public class C: I
En el ejemplo
C#
abstract class A
int y;
get { return 0; }
get { return y; }
set { y = value; }
class B: A
int z;
get { return z; }
set { z = value; }
C#
public class B
get {...}
public class D: B
Events
Un *evento _ es un miembro que permite a un objeto o una clase proporcionar
notificaciones. Los clientes pueden adjuntar código ejecutable para eventos
proporcionando _ controladores de eventos *.
antlr
event_declaration
event_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'virtual'
| 'sealed'
| 'override'
| 'abstract'
| 'extern'
| event_modifier_unsafe
event_accessor_declarations
: add_accessor_declaration remove_accessor_declaration
| remove_accessor_declaration add_accessor_declaration
add_accessor_declaration
remove_accessor_declaration
Las declaraciones de eventos están sujetas a las mismas reglas que las declaraciones de
método (métodos) con respecto a las combinaciones válidas de modificadores.
Cuando una declaración de evento incluye un extern modificador, se dice que el evento
es un *evento externo. Dado que una declaración de evento externo no proporciona
ninguna implementación real, es un error que incluya tanto el extern modificador como
el _event_accessor_declarations *.
Puesto que += y -= son las únicas operaciones que se permiten en un evento fuera del
tipo que declara el evento, el código externo puede Agregar y quitar controladores para
un evento, pero no puede obtener o modificar la lista subyacente de controladores de
eventos.
En una operación de la forma x += y o x -= y , cuando x es un evento y la referencia
tiene lugar fuera del tipo que contiene la declaración de x , el resultado de la operación
tiene el tipo void (en lugar de tener el tipo de x , con el valor de x después de la
asignación). Esta regla prohíbe que el código externo examine indirectamente el
delegado subyacente de un evento.
C#
Button OkButton;
Button CancelButton;
public LoginDialog() {
En este caso, el LoginDialog constructor de instancia crea dos Button instancias y asocia
los controladores de eventos a los Click eventos.
En el ejemplo
C#
Click = null;
Click se usa como un campo dentro de la Button clase. Como muestra el ejemplo, el
campo se puede examinar, modificar y usar en expresiones de invocación de delegado.
El OnClick método de la Button clase "genera" el Click evento. La noción de generar
un evento es equivalente exactamente a invocar el delegado representado por el
evento; por lo tanto, no hay ninguna construcción especial de lenguaje para generar
eventos. Tenga en cuenta que la invocación del delegado está precedida por una
comprobación que garantiza que el delegado no es NULL.
C#
C#
C#
class X
C#
class X
public event D Ev {
add {
remove {
En el ejemplo
C#
// MouseDown event
// MouseUp event
MouseEventHandler handler;
handler = (MouseEventHandler)GetEventHandler(mouseUpEventKey);
if (handler != null)
handler(this, args);
Las diferencias entre los miembros estáticos y de instancia se tratan más adelante en
miembros estáticos y de instancia.
Una abstract declaración de evento especifica que los descriptores de acceso del
evento son virtuales, pero no proporciona una implementación real de los descriptores
de acceso. En su lugar, las clases derivadas no abstractas deben proporcionar su propia
implementación para los descriptores de acceso invalidando el evento. Dado que una
declaración de evento abstracto no proporciona ninguna implementación real, no
puede proporcionar event_accessor_declarations delimitados por llaves.
Indizadores
*Indexer _ es un miembro que permite indizar un objeto de la misma manera que una
matriz. Los indexadores se declaran mediante _indexer_declaration * s:
antlr
indexer_declaration
indexer_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'virtual'
| 'sealed'
| 'override'
| 'abstract'
| 'extern'
| indexer_modifier_unsafe
indexer_declarator
indexer_body
Las declaraciones de indexador están sujetas a las mismas reglas que las declaraciones
de método (métodos) con respecto a las combinaciones válidas de modificadores, con
la única excepción de que el modificador static no se permite en una declaración de
indexador.
El tipo de un indexador y cada uno de los tipos a los que se hace referencia en el
formal_parameter_list deben ser al menos tan accesibles como el propio indizador
(restricciones de accesibilidad).
Un cuerpo de expresión que consta de " => " seguido de una expresión E y un punto y
coma es exactamente equivalente al cuerpo de la instrucción { get { return E; } } y,
por tanto, solo se puede usar para especificar indizadores de solo captador en los que
una sola expresión proporciona el resultado del captador.
Aunque la sintaxis para tener acceso a un elemento de indexador es la misma que para
un elemento de matriz, un elemento de indexador no se clasifica como una variable. Por
lo tanto, no es posible pasar un elemento indexador como ref argumento o out .
La firma de un indizador debe ser diferente de las firmas de todos los demás
indexadores declarados en la misma clase.
Los indexadores y las propiedades son muy similares en concepto, pero difieren de las
siguientes maneras:
Aparte de estas diferencias, todas las reglas definidas en los descriptores de acceso y las
propiedades implementadas automáticamente se aplican a los descriptores de acceso
del indizador y a los descriptores de acceso de propiedades.
C#
using System;
class BitArray
int[] bits;
int length;
this.length = length;
get {
set {
if (value) {
else {
C#
class CountPrimes
int count = 1;
if (!flags[i]) {
count++;
return count;
Tenga en cuenta que la sintaxis para tener acceso a los elementos de BitArray es
exactamente igual que para bool[] .
C#
using System;
class Grid
get {
c = Char.ToUpper(c);
set {
c = Char.ToUpper(c);
Operadores
Un *operador _ es un miembro que define el significado de un operador de expresión
que se puede aplicar a las instancias de la clase. Los operadores se declaran mediante
_operator_declaration * s:
antlr
operator_declaration
operator_modifier
: 'public'
| 'static'
| 'extern'
| operator_modifier_unsafe
operator_declarator
: unary_operator_declarator
| binary_operator_declarator
| conversion_operator_declarator
unary_operator_declarator
overloadable_unary_operator
binary_operator_declarator
overloadable_binary_operator
conversion_operator_declarator
operator_body
: block
| ';'
Al igual que otros miembros, las clases derivadas heredan los operadores declarados en
una clase base. Dado que las declaraciones de operador siempre requieren la clase o
estructura en la que se declara que el operador participa en la firma del operador, no es
posible que un operador declarado en una clase derivada oculte un operador declarado
en una clase base. Por lo tanto, el new modificador nunca es necesario y, por lo tanto,
nunca se permite en una declaración de operador.
Operadores unarios
Las siguientes reglas se aplican a las declaraciones de operadores unarios, donde T
denota el tipo de instancia de la clase o el struct que contiene la declaración de
operador:
C#
temp[i] = iv[i] + 1;
return temp;
class Test
IntVector iv2;
Operadores binarios
Las siguientes reglas se aplican a las declaraciones de operadores binarios, donde T
denota el tipo de instancia de la clase o el struct que contiene la declaración de
operador:
operator == y operator !=
Operadores de conversión
Una declaración de operador de conversión introduce una conversión definida por el
usuario (conversiones definidas por el usuario) que aumenta las conversiones implícitas
y explícitas predefinidas.
operador.
Ni S0 ni T0 es un interface_type.
Sin incluir las conversiones definidas por el usuario, no existe una conversión de S
a T o de T a S .
En el ejemplo
C#
las dos primeras declaraciones de operador se permiten porque, para los fines de los
indizadores. 3, T y int y string respectivamente se consideran tipos únicos sin
relación. Sin embargo, el tercer operador es un error porque C<T> es la clase base de
D<T> .
Sin embargo, es posible declarar operadores en tipos genéricos que, para argumentos
de tipo concretos, especifiquen conversiones que ya existan como conversiones
predefinidas. En el ejemplo
C#
struct Convertible<T>
En los casos en los que existe una conversión predefinida entre dos tipos, se omiten las
conversiones definidas por el usuario entre esos tipos. Concretamente:
En todos los tipos object , pero los operadores declarados por el Convertible<T> tipo
anterior no entran en conflicto con las conversiones predefinidas. Por ejemplo:
C#
i = n; // Error
Sin embargo, para object el tipo, las conversiones predefinidas ocultan las conversiones
definidas por el usuario en todos los casos, pero una:
C#
En general, las conversiones implícitas definidas por el usuario deben diseñarse para
que nunca se produzcan excepciones y nunca se pierda información. Si una conversión
definida por el usuario puede dar lugar a excepciones (por ejemplo, porque el
argumento de origen está fuera del intervalo) o la pérdida de información (como
descartar los bits de orden superior), esa conversión debe definirse como una
conversión explícita.
En el ejemplo
C#
using System;
byte value;
this.value = value;
return d.value;
}
Constructores de instancias
Un *constructor de instancia _ es un miembro que implementa las acciones necesarias
para inicializar una instancia de una clase. Los constructores de instancias se declaran
mediante _constructor_declaration * s:
antlr
constructor_declaration
constructor_modifier
: 'public'
| 'protected'
| 'internal'
| 'private'
| 'extern'
| constructor_modifier_unsafe
;
constructor_declarator
constructor_initializer
constructor_body
: block
| ';'
No se heredan los constructores de instancia. Por lo tanto, una clase no tiene ningún
constructor de instancia que no sea el declarado realmente en la clase. Si una clase no
contiene ninguna declaración de constructor de instancia, se proporciona
automáticamente un constructor de instancia predeterminado (constructores
predeterminados).
C#
C(...) {...}
es exactamente equivalente a
C#
C#
class A
class B: A
Dado el ejemplo
C#
using System;
class A
public A() {
PrintFields();
class B: A
int x = 1;
int y;
public B() {
y = -1;
Cuando new B() se utiliza para crear una instancia de B , se genera el siguiente
resultado:
Consola
x = 1, y = 0
C#
using System;
using System.Collections;
class A
public A() {
count = 0;
public A(int n) {
count = n;
class B: A
int max;
items.Add("default");
max = n;
C#
using System.Collections;
class A
int x, y, count;
public A() {
x = 1; // Variable initializer
count = 0;
public A(int n) {
x = 1; // Variable initializer
count = n;
class B: A
double sqrt2;
ArrayList items;
int max;
items.Add("default");
max = n;
Constructores predeterminados
Si una clase no contiene ninguna declaración de constructor de instancia, se
proporciona automáticamente un constructor de instancia predeterminado. Ese
constructor predeterminado simplemente invoca el constructor sin parámetros de la
clase base directa. Si la clase es abstracta, se protege la accesibilidad declarada para el
constructor predeterminado. De lo contrario, la accesibilidad declarada para el
constructor predeterminado es Public. Por lo tanto, el constructor predeterminado
siempre tiene el formato
C#
or
C#
En el ejemplo
C#
class Message
object sender;
string text;
C#
class Message
object sender;
string text;
Constructores privados
Cuando una clase T declara solo constructores de instancia privados, no es posible que
las clases fuera del texto del programa de T deriven de T o creen directamente
instancias de T . Por lo tanto, si una clase solo contiene miembros estáticos y no está
pensado para que se creen instancias, agregar un constructor de instancia privado vacío
impedirá la creación de instancias. Por ejemplo:
C#
La Trig clase agrupa los métodos relacionados y las constantes, pero no está previsto
que se creen instancias de ellos. Por lo tanto, declara un único constructor de instancia
privado vacío. Se debe declarar al menos un constructor de instancia para suprimir la
generación automática de un constructor predeterminado.
C#
class Text
Constructores estáticos
Un *constructor estático _ es un miembro que implementa las acciones necesarias para
inicializar un tipo de clase cerrada. Los constructores estáticos se declaran mediante
_static_constructor_declaration * s:
antlr
static_constructor_declaration
static_constructor_modifiers
: 'extern'? 'static'
| 'static' 'extern'?
| static_constructor_modifiers_unsafe
static_constructor_body
: block
| ';'
El constructor estático de un tipo de clase cerrada se ejecuta como máximo una vez en
un dominio de aplicación determinado. La ejecución de un constructor estático lo
desencadena el primero de los siguientes eventos para que se produzca dentro de un
dominio de aplicación:
Para inicializar un nuevo tipo de clase cerrada, primero se crea un nuevo conjunto de
campos estáticos (campos estáticos y de instancia) para ese tipo cerrado en particular.
Cada uno de los campos estáticos se inicializa en su valor predeterminado (valores
predeterminados). A continuación, se ejecutan los inicializadores de campo estáticos
(inicialización de campos estáticos) para esos campos estáticos. Por último, se ejecuta el
constructor estático.
En el ejemplo
C#
using System;
class Test
A.F();
B.F();
class A
static A() {
Console.WriteLine("Init A");
Console.WriteLine("A.F");
}
class B
static B() {
Console.WriteLine("Init B");
Console.WriteLine("B.F");
}
Consola
Init A
A.F
Init B
B.F
Es posible crear dependencias circulares que permitan observar los campos estáticos
con inicializadores variables en su estado de valor predeterminado.
En el ejemplo
C#
using System;
class A
static A() {
X = B.Y + 1;
class B
static B() {}
genera el resultado
Consola
X = 1, Y = 2
Para ejecutar el Main método, el sistema primero ejecuta el inicializador para B.Y , antes
B del constructor estático de la clase. Y el inicializador de hace que A el constructor
estático de se ejecute porque A.X se hace referencia al valor de. El constructor estático
de A a su vez continúa para calcular el valor de X y, al hacerlo, recupera el valor
predeterminado de Y , que es cero. A.X por tanto, se inicializa en 1. El proceso de
ejecución de los A inicializadores de campo estáticos y del constructor estático se
completa y vuelve al cálculo del valor inicial Y de, cuyo resultado es 2.
Dado que el constructor estático se ejecuta exactamente una vez para cada tipo de clase
construido cerrado, es un lugar cómodo para aplicar comprobaciones en tiempo de
ejecución en el parámetro de tipo que no se puede comprobar en tiempo de
compilación a través de restricciones (restricciones de parámetros de tipo). Por ejemplo,
el tipo siguiente usa un constructor estático para exigir que el argumento de tipo sea
una enumeración:
C#
static Gen() {
if (!typeof(T).IsEnum) {
Destructores
Un *destructor _ es un miembro que implementa las acciones necesarias para destruir
una instancia de una clase. Un destructor se declara mediante una
_destructor_declaration *:
antlr
destructor_declaration
| destructor_declaration_unsafe
destructor_body
: block
| ';'
Los destructores no se heredan. Por lo tanto, una clase no tiene ningún destructor que
no sea el que se puede declarar en esa clase.
Dado que un destructor debe tener ningún parámetro, no se puede sobrecargar, por lo
que una clase puede tener, como máximo, un destructor.
C#
using System;
class A
~A() {
Console.WriteLine("A's destructor");
class B: A
~B() {
Console.WriteLine("B's destructor");
class Test
B b = new B();
b = null;
GC.Collect();
GC.WaitForPendingFinalizers();
is
B's destructor
A's destructor
Dado que se llama a los destructores en una cadena de herencia en orden, desde la más
derivada hasta la menos derivada.
C#
class A
this.Finalize(); // error
class A
Para obtener una explicación del comportamiento cuando se produce una excepción
desde un destructor, vea cómo se controlan las excepciones.
Iterators
Un miembro de función (miembros de función) implementado mediante un bloque de
iteradores (bloques) se denomina iterador.
Interfaces de enumerador
Las interfaces de enumerador son la interfaz no genérica
System.Collections.IEnumerator y todas las creaciones de instancias de la interfaz
genérica System.Collections.Generic.IEnumerator<T> . Por motivos de brevedad, en
este capítulo se hace referencia a estas interfaces como IEnumerator y IEnumerator<T> ,
respectivamente.
Interfaces enumerables
Las interfaces enumerables son la interfaz no genérica System.Collections.IEnumerable
y todas las creaciones de instancias de la interfaz genérica
System.Collections.Generic.IEnumerable<T> . Por motivos de brevedad, en este capítulo
se hace referencia a estas interfaces como IEnumerable y IEnumerable<T> ,
respectivamente.
Tipo yield
Un iterador genera una secuencia de valores, todo el mismo tipo. Este tipo se denomina
tipo yield del iterador.
Objetos Enumerator
Cuando un miembro de función que devuelve un tipo de interfaz de enumerador se
implementa mediante un bloque de iteradores, al invocar al miembro de función no se
ejecuta inmediatamente el código en el bloque de iteradores. En su lugar, se crea y se
devuelve un objeto de enumerador . Este objeto encapsula el código especificado en el
bloque de iteradores y la ejecución del código en el bloque de iterador se produce
cuando se invoca el método del objeto de enumerador MoveNext . Un objeto de
enumerador tiene las siguientes características:
Un objeto de enumerador suele ser una instancia de una clase de enumerador generada
por el compilador que encapsula el código en el bloque de iteradores e implementa las
interfaces del enumerador, pero otros métodos de implementación son posibles. Si el
compilador genera una clase de enumerador, esa clase se anidará, directa o
indirectamente, en la clase que contiene el miembro de función, tendrá accesibilidad
privada y tendrá un nombre reservado para uso del compilador (identificadores).
El método MoveNext
El MoveNext método de un objeto de enumerador encapsula el código de un bloque de
iteradores. Al invocar el MoveNext método, se ejecuta código en el bloque de iterador y
se establece la Current propiedad del objeto de enumerador según corresponda. La
acción precisa realizada por MoveNext depende del estado del objeto de enumerador
cuando MoveNext se invoca:
Propiedad actual
El método Dispose
excepción y se propague fuera del cuerpo del iterador, el estado del objeto de
enumerador se establece en después de y la excepción se propaga al llamador
del Dispose método.
Cambia el estado a después de.
Si el estado del objeto de enumerador es After, la invocación de Dispose no tiene
ningún efecto.
Objetos enumerables
Cuando un miembro de función que devuelve un tipo de interfaz enumerable se
implementa mediante un bloque de iteradores, al invocar al miembro de función no se
ejecuta inmediatamente el código en el bloque de iteradores. En su lugar, se crea y se
devuelve un objeto enumerable . El método del objeto enumerable GetEnumerator
devuelve un objeto de enumerador que encapsula el código especificado en el bloque
de iterador y la ejecución del código en el bloque de iterador se produce cuando se
invoca el método del objeto de enumerador MoveNext . Un objeto enumerable tiene las
siguientes características:
El método GetEnumerator
Ejemplo de implementación
En esta sección se describe una posible implementación de iteradores en términos de
construcciones estándar de C#. La implementación que se describe aquí se basa en los
mismos principios utilizados por el compilador de Microsoft C#, pero no es una
implementación asignada o la única posible.
C#
using System;
using System.Collections;
using System.Collections.Generic;
T[] items;
int count;
if (items == null) {
items = newItems;
items[count++] = item;
public T Pop() {
T result = items[--count];
items[count] = default(T);
return result;
C#
...
int __state;
T __current;
Stack<T> __this;
int i;
this.__this = __this;
public T Current {
object IEnumerator.Current {
switch (__state) {
i = __this.count - 1;
__loop:
__current = __this.items[i];
__state = 1;
return true;
__state1:
--i;
goto __loop;
__state2:
__state = 2;
return false;
__state = 2;
void IEnumerator.Reset() {
C#
using System;
using System.Collections.Generic;
class Test
foreach (int x in e) {
foreach (int y in e) {
Console.WriteLine();
El FromTo método se puede traducir en una instancia de una clase Enumerable generada
por el compilador que encapsula el código en el bloque de iteradores, como se muestra
a continuación.
C#
using System;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
class Test
...
class __Enumerable1:
IEnumerable<int>, IEnumerable,
IEnumerator<int>, IEnumerator
int __state;
int __current;
int __from;
int from;
int to;
int i;
this.__from = __from;
this.to = to;
if (Interlocked.CompareExchange(ref __state, 1, 0) != 0) {
result.__state = 1;
result.from = result.__from;
return result;
IEnumerator IEnumerable.GetEnumerator() {
return (IEnumerator)GetEnumerator();
object IEnumerator.Current {
switch (__state) {
case 1:
__current = from++;
__state = 1;
return true;
case 2:
__state = 2;
return false;
default:
__state = 2;
void IEnumerator.Reset() {
subprocesos.
C#
using System;
using System.Collections.Generic;
T value;
Tree<T> left;
Tree<T> right;
this.value = value;
this.left = left;
this.right = right;
yield value;
class Program
MakeTree(items, i + 1, right));
// 1 2 3 4 5 6 7 8 9
Console.WriteLine();
Console.WriteLine();
C#
...
Node<T> __this;
int __state;
T __current;
this.__this = __this;
public T Current {
object IEnumerator.Current {
try {
switch (__state) {
case 0:
__state = -1;
__left = __this.left.GetEnumerator();
goto case 1;
case 1:
__state = -2;
__current = __left.Current;
__state = 1;
return true;
__left_dispose:
__state = -1;
__left.Dispose();
__yield_value:
__current = __this.value;
__state = 2;
return true;
case 2:
__state = -1;
__right = __this.right.GetEnumerator();
goto case 3;
case 3:
__state = -3;
__current = __right.Current;
__state = 3;
return true;
__right_dispose:
__state = -1;
__right.Dispose();
__end:
__state = 4;
break;
finally {
return false;
try {
switch (__state) {
case 1:
case -2:
__left.Dispose();
break;
case 3:
case -3:
__right.Dispose();
break;
finally {
__state = 4;
void IEnumerator.Reset() {
Funciones asincrónicas
Un método (métodos) o una función anónima (expresiones de función anónima) con el
async modificador se denomina *Async function _. En general, el término _ Async* se
usa para describir cualquier tipo de función que tenga el async modificador.
El return_type de un método asincrónico debe ser void o un tipo de tarea. Los tipos de
tarea son System.Threading.Tasks.Task y los tipos construidos a partir de
System.Threading.Tasks.Task<T> . Por motivos de brevedad, en este capítulo se hace
referencia a estos tipos como Task y Task<T> , respectivamente. Se dice que un método
asincrónico que devuelve un tipo de tarea es el que devuelve la tarea.
Cuando finaliza el cuerpo de la función asincrónica, la tarea devuelta se saca del estado
incompleto:
Las estructuras son similares a las clases en que representan las estructuras de datos
que pueden contener miembros de datos y miembros de función. Sin embargo, a
diferencia de las clases, las estructuras son tipos de valor y no requieren la asignación
del montón. Una variable de un tipo de estructura contiene directamente los datos del
struct, mientras que una variable de un tipo de clase contiene una referencia a los datos,
la última conocida como un objeto.
Los structs son particularmente útiles para estructuras de datos pequeñas que tengan
semánticas de valor. Los números complejos, los puntos de un sistema de coordenadas
o los pares clave-valor de un diccionario son buenos ejemplos de structs. La clave de
estas estructuras de datos es que tienen pocos miembros de datos, que no requieren el
uso de la herencia o la identidad referencial, y que se pueden implementar de forma
cómoda mediante la semántica de valores donde la asignación copia el valor en lugar
de la referencia.
Como se describe en tipos simples, los tipos simples proporcionados por C#, como int
, double y bool , son en realidad todos los tipos de estructura. Del mismo modo que
estos tipos predefinidos son Structs, también es posible usar estructuras y sobrecarga
de operadores para implementar nuevos tipos "primitivos" en el lenguaje C#. Al final de
este capítulo (ejemplos de struct) se proporcionan dos ejemplos de estos tipos.
Declaraciones de estructuras
Una struct_declaration es una type_declaration (declaraciones de tipos) que declara un
nuevo struct:
antlr
struct_declaration
Modificadores de struct
Un struct_declaration puede incluir opcionalmente una secuencia de modificadores de
struct:
antlr
struct_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| struct_modifier_unsafe
Los modificadores de una declaración de struct tienen el mismo significado que los de
una declaración de clase (declaraciones de clase).
Unmodifier (modificador)
El partial modificador indica que este struct_declaration es una declaración de tipos
parciales. Varias declaraciones de struct parciales con el mismo nombre dentro de una
declaración de tipo o espacio de nombres envolvente se combinan para formar una
declaración de struct, siguiendo las reglas especificadas en tipos parciales.
Interfaces de struct
Una declaración de estructura puede incluir una especificación de struct_interfaces , en
cuyo caso se dice que el struct implementa directamente los tipos de interfaz
especificados.
antlr
struct_interfaces
: ':' interface_type_list
antlr
struct_body
Miembros de estructuras
Los miembros de una estructura se componen de los miembros introducidos por su
struct_member_declaration s y los miembros heredados del tipo System.ValueType .
antlr
struct_member_declaration
: constant_declaration
| field_declaration
| method_declaration
| property_declaration
| event_declaration
| indexer_declaration
| operator_declaration
| constructor_declaration
| static_constructor_declaration
| type_declaration
| struct_member_declaration_unsafe
Semántica de valores
Los Structs son tipos de valor (tipos de valor) y se dice que tienen semántica de valor.
Por otro lado, las clases son tipos de referencia (tipos de referencia) y se dice que tienen
semántica de referencia.
Una variable de un tipo de estructura contiene directamente los datos del struct,
mientras que una variable de un tipo de clase contiene una referencia a los datos, la
última conocida como un objeto. Cuando un struct B contiene un campo de instancia
de tipo A y A es un tipo de estructura, es un error en tiempo de compilación para A
que dependa de B o un tipo construido a partir de B . Un struct X * depende
directamente de _ un struct Y si X contiene un campo de instancia de tipo Y . Dada
esta definición, el conjunto completo de estructuras de las que depende un struct es el
cierre transitivo de la *función _ depende directamente de**. Por ejemplo
C#
struct Node
int data;
es un error porque Node contiene un campo de instancia de su propio tipo. Otro ejemplo
C#
struct A { B b; }
struct B { C c; }
struct C { A a; }
Con las clases, es posible que dos variables hagan referencia al mismo objeto y, por lo
tanto, las operaciones en una variable afecten al objeto al que hace referencia la otra
variable. Con las estructuras, cada variable tiene su propia copia de los datos (excepto
en el caso de ref out las variables de parámetro y), y no es posible que las operaciones
en una afecten a la otra. Además, dado que los Structs no son tipos de referencia, no es
posible que los valores de un tipo struct sea null .
Dada la declaración
C#
struct Point
public int x, y;
this.x = x;
this.y = y;
fragmento de código
C#
Point b = a;
a.x = 100;
System.Console.WriteLine(b.x);
genera el valor 10 . La asignación de a para b crea una copia del valor y, b por tanto,
no se ve afectada por la asignación a a.x . Point En su lugar se ha declarado como una
clase, el resultado sería 100 porque a y b haría referencia al mismo objeto.
Herencia
Todos los tipos de struct se heredan implícitamente de la clase System.ValueType , que,
a su vez, hereda de la clase object . Una declaración de estructura puede especificar
una lista de interfaces implementadas, pero no es posible que una declaración de struct
especifique una clase base.
Los tipos de struct nunca son abstractos y siempre están sellados implícitamente.
abstract sealed Por lo tanto, los modificadores y no se permiten en una declaración de
estructura.
System.ValueType .
Asignación
La asignación a una variable de un tipo de struct crea una copia del valor que se va a
asignar. Esto difiere de la asignación a una variable de un tipo de clase, que copia la
referencia pero no el objeto identificado por la referencia.
De forma similar a una asignación, cuando se pasa un struct como parámetro de valor o
se devuelve como resultado de un miembro de función, se crea una copia de la
estructura. Un struct se puede pasar por referencia a un miembro de función mediante
ref un out parámetro o.
Valores predeterminados
Tal y como se describe en valores predeterminados, varios tipos de variables se
inicializan automáticamente en su valor predeterminado cuando se crean. En el caso de
las variables de tipos de clase y otros tipos de referencia, este valor predeterminado es
null . Sin embargo, dado que los Structs son tipos de valor que no pueden ser null , el
valor predeterminado de un struct es el valor generado al establecer todos los campos
de tipo de valor en sus valores predeterminados y todos los campos de tipo de
referencia en null .
C#
Inicializa cada Point de la matriz con el valor generado al establecer los x campos y y
en cero.
C#
using System;
struct KeyValuePair
string key;
string value;
this.key = key;
this.value = value;
el constructor de instancia definido por el usuario protege solo los valores NULL cuando
se llama explícitamente. En los casos en KeyValuePair los que una variable está sujeta a
la inicialización de valores predeterminados, los key campos y serán value null y el
struct debe estar preparado para controlar este estado.
Dado que los Structs no son tipos de referencia, estas operaciones se implementan de
forma diferente para los tipos de struct. Cuando un valor de un tipo de estructura se
convierte al tipo object o a un tipo de interfaz implementado por el struct, se produce
una operación de conversión boxing. Del mismo modo, cuando un valor de tipo object
o un valor de un tipo de interfaz se vuelve a convertir a un tipo de estructura, se
produce una operación de conversión unboxing. Una diferencia clave de las mismas
operaciones en los tipos de clase es que la conversión boxing y la conversión unboxing
copia el valor de la estructura dentro o fuera de la instancia de conversión boxing. Por lo
tanto, después de una operación de conversión boxing o unboxing, los cambios
realizados en la estructura desempaquetada no se reflejan en la estructura con
conversión boxing.
C#
using System;
struct Counter
int value;
value++;
return value.ToString();
class Program
T x = new T();
Console.WriteLine(x.ToString());
Console.WriteLine(x.ToString());
Console.WriteLine(x.ToString());
Test<Counter>();
Consola
C#
using System;
interface ICounter
void Increment();
int value;
return value.ToString();
void ICounter.Increment() {
value++;
class Program
T x = new T();
Console.WriteLine(x);
x.Increment(); // Modify x
Console.WriteLine(x);
Console.WriteLine(x);
Test<Counter>();
Consola
Para obtener más información sobre las conversiones boxing y unboxing, consulte
Boxing y unboxing.
Significado de este
Dentro de un constructor de instancia o un miembro de función de instancia de una
clase, this se clasifica como un valor. Por lo tanto, aunque se this puede usar para
hacer referencia a la instancia de a la que se invocó el miembro de función, no es
posible asignar a this en un miembro de función de una clase.
Inicializadores de campo
Como se describe en valores predeterminados, el valor predeterminado de un struct
consta del valor que se obtiene al establecer todos los campos de tipo de valor en sus
valores predeterminados y todos los campos de tipo de referencia en null . Por esta
razón, un struct no permite que las declaraciones de campo de instancia incluyan
inicializadores variables. Esta restricción solo se aplica a los campos de instancia. Los
campos estáticos de un struct pueden incluir inicializadores variables.
En el ejemplo
C#
struct Point
Constructores
A diferencia de una clase, un struct no puede declarar un constructor de instancia sin
parámetros. En su lugar, cada struct tiene implícitamente un constructor de instancia sin
parámetros que siempre devuelve el valor que se obtiene al establecer todos los
campos de tipo de valor en sus valores predeterminados y todos los campos de tipo de
referencia en null (constructores predeterminados). Un struct puede declarar
constructores de instancia que tengan parámetros. Por ejemplo
C#
struct Point
int x, y;
this.x = x;
this.y = y;
C#
Point p1 = new Point();
Point p2 = new Point(0, 0);
C#
struct Point
int x, y;
public int X {
set { x = value; }
public int Y {
set { y = value; }
C#
struct Point
Destructores
Un struct no puede declarar un destructor.
Constructores estáticos
Los constructores estáticos para Structs siguen la mayoría de las mismas reglas que para
las clases. La ejecución de un constructor estático para un tipo de struct lo desencadena
el primero de los siguientes eventos para que se produzca dentro de un dominio de
aplicación:
Ejemplos de estructuras
A continuación se muestran dos ejemplos importantes del uso de struct tipos para
crear tipos que se pueden usar de forma similar a los tipos predefinidos del lenguaje,
pero con semántica modificada.
C#
using System;
// When the defined field is true, this DBInt represents a known value
// which is stored in the value field. When the defined field is false,
int value;
bool defined;
DBInt(int value) {
this.value = value;
this.defined = true;
return x.value;
}
return x;
DBInt x = (DBInt)obj;
return value;
C#
using System;
sbyte value;
DBBool(int value) {
this.value = (sbyte)value;
// false to DBBool.False.
// otherwise.
// otherwise.
return value;
return "DBBool.Null";
Matrices
Artículo • 16/09/2021 • Tiempo de lectura: 9 minutos
Una matriz es una estructura de datos que contiene un número de variables a las que se
tiene acceso a través de índices calculados. Las variables contenidas en una matriz,
denominadas también elementos de la matriz, son todas del mismo tipo y este tipo se
conoce como tipo de elemento de la matriz.
Una matriz tiene un rango que determina el número de índices asociados a cada
elemento de la matriz. El rango de una matriz también se conoce como las dimensiones
de la matriz. Una matriz con un rango de uno se denomina *matriz unidimensional _.
Una matriz con un rango mayor que uno se denomina matriz multidimensional* *. Las
matrices multidimensionales de tamaño específico se suelen denominar matrices
bidimensionales, matrices tridimensionales, etc.
Cada dimensión de una matriz tiene una longitud asociada que es un número entero
mayor o igual que cero. Las longitudes de las dimensiones no forman parte del tipo de
la matriz, sino que se establecen cuando se crea una instancia del tipo de matriz en
tiempo de ejecución. La longitud de una dimensión determina el intervalo válido de
índices de esa dimensión: para una dimensión de longitud, los N índices pueden oscilar
entre 0 y N - 1 ambos inclusive. El número total de elementos de una matriz es el
producto de las longitudes de cada dimensión de la matriz. Si una o varias de las
dimensiones de una matriz tienen una longitud de cero, se dice que la matriz está vacía.
El tipo de elemento de una matriz puede ser cualquiera, incluido un tipo de matriz.
Tipos de matriz
Un tipo de matriz se escribe como un non_array_type seguido de uno o varios
rank_specifier s:
array_type
: non_array_type rank_specifier+
non_array_type
: type
rank_specifier
dim_separator
: ','
Un tipo de matriz con el formato T[R] es una matriz con rango R y un tipo de
elemento que no es de matriz T .
Un tipo de matriz con el formato T[R][R1]...[Rn] es una matriz con rango R y un
tipo de elemento T[R1]...[Rn] .
En efecto, los rank_specifier s se leen de izquierda a derecha antes del tipo de elemento
final que no es de matriz. El tipo int[][,,][,] es una matriz unidimensional de matrices
tridimensionales de matrices bidimensionales de int .
En tiempo de ejecución, un valor de un tipo de matriz puede ser null o una referencia a
una instancia de ese tipo de matriz.
En tiempo de ejecución, un valor de tipo System.Array puede ser null o una referencia
a una instancia de cualquier tipo de matriz.
using System.Collections.Generic;
class Test
Siempre que hay una conversión de referencia implícita o explícita de S[] a IList<T> ,
también hay una conversión de referencia explícita desde IList<T> y sus interfaces base
a S[] (conversiones de referencia explícitas).
Los elementos de una matriz se pueden enumerar mediante una foreach instrucción (la
instrucción foreach).
Miembros de la matriz
Cada tipo de matriz hereda los miembros declarados por el System.Array tipo.
Covarianza de matrices
Para dos reference_type s A y B , si una conversión de referencia implícita (conversiones
de referencia implícita) o una conversión de referencia explícita (conversiones de
referencia explícita) existen de A a B , la misma conversión de referencia también existe
desde el tipo de matriz A[R] al tipo de matriz B[R] , donde R es cualquier rank_specifier
determinado (pero lo mismo para ambos tipos de matriz). Esta relación se conoce como
covarianza de matriz. En particular, la covarianza de matrices significa que un valor de
un tipo de matriz A[R] puede ser realmente una referencia a una instancia de un tipo de
matriz B[R] , siempre que exista una conversión de referencia implícita de B a A .
class Test
static void Fill(object[] array, int index, int count, object value) {
Main , las dos primeras invocaciones de Fill se realizan correctamente, pero la tercera
invocación hace System.ArrayTypeMismatchException que se produzca una excepción al
ejecutar la primera asignación a array[i] . La excepción se produce porque int no se
puede almacenar una conversión boxing en una string matriz.
Inicializadores de matriz
Los inicializadores de matriz se pueden especificar en declaraciones de campo (campos),
declaraciones de variables locales (declaraciones de variables locales) y expresiones de
creación de matrices (expresiones de creación de matrices):
array_initializer
variable_initializer_list
variable_initializer
: expression
| array_initializer
int[,] b = {{0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}};
crea una matriz bidimensional con una longitud de cinco para la dimensión situada más
a la izquierda y una longitud de dos para la dimensión situada más a la derecha:
b[0, 0] = 0; b[0, 1] = 1;
b[1, 0] = 2; b[1, 1] = 3;
b[2, 0] = 4; b[2, 1] = 5;
b[3, 0] = 6; b[3, 1] = 7;
b[4, 0] = 8; b[4, 1] = 9;
int[,] c = {};
crea una matriz bidimensional con una longitud de cero para la dimensión situada más
a la izquierda y la derecha:
int[,] c = new int[0, 0];
int i = 3;
Una interfaz define un contrato. Una clase o estructura que implementa una interfaz
debe adherirse a su contrato. Una interfaz puede heredar de varias interfaces base, y
una clase o estructura puede implementar varias interfaces.
Declaraciones de interfaz
Una interface_declaration es una type_declaration (declaraciones de tipos) que declara
un nuevo tipo de interfaz.
antlr
interface_declaration
Modificadores de interfaz
Un interface_declaration puede incluir opcionalmente una secuencia de modificadores
de interfaz:
antlr
interface_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| interface_modifier_unsafe
Unmodifier (modificador)
El partial modificador indica que este interface_declaration es una declaración de tipos
parciales. Varias declaraciones de interfaz parcial con el mismo nombre dentro de una
declaración de tipo o espacio de nombres envolvente se combinan para formar una
declaración de interfaz, siguiendo las reglas especificadas en tipos parciales.
antlr
variant_type_parameter_list
variant_type_parameters
variance_annotation
: 'in'
| 'out'
En el ejemplo
C#
X M(Y y);
Z P { get; set; }
Seguridad de la varianza
La aparición de anotaciones de varianza en la lista de parámetros de tipo de un tipo
restringe los lugares en los que se pueden producir tipos en la declaración de tipos.
Conversión de varianza
El propósito de las anotaciones de varianza es proporcionar conversiones más flexibles
(pero todavía con seguridad de tipos) a los tipos de interfaz y delegado. Para este fin, las
definiciones de las conversiones implícitas (conversionesimplícitas) y explícitas
(conversiones explícitas) hacen uso de la noción de Variance-Convertibility, que se
define de la siguiente manera:
Ai a Bi
Xi es contravariante y existe una referencia implícita o una conversión de
identidad de Bi a Ai
Xi es invariable y existe una conversión de identidad de Ai a Bi
Interfaces base
Una interfaz puede heredar de cero o más tipos de interfaz, que se denominan
interfaces base explícitas de la interfaz. Cuando una interfaz tiene una o más interfaces
base explícitas, en la declaración de esa interfaz, el identificador de interfaz va seguido
de un signo de dos puntos y una lista separada por comas de tipos de interfaz base.
antlr
interface_base
: ':' interface_type_list
Las interfaces base explícitas de una interfaz deben ser al menos tan accesibles como la
propia interfaz (restricciones de accesibilidad). Por ejemplo, se trata de un error en
tiempo de compilación para especificar una private internal interfaz o en el
interface_base de una public interfaz.
Se trata de un error en tiempo de compilación para una interfaz que hereda directa o
indirectamente de sí misma.
Las interfaces base de una interfaz son las interfaces base explícitas y sus interfaces
base. En otras palabras, el conjunto de interfaces base es el cierre transitivo completo de
las interfaces base explícitas, sus interfaces base explícitas, etc. Una interfaz hereda
todos los miembros de sus interfaces base. En el ejemplo
C#
interface IControl
void Paint();
Cada interfaz base de una interfaz debe ser segura para la salida (seguridad de
lavarianza). Una clase o estructura que implementa una interfaz también implementa
implícitamente todas las interfaces base de la interfaz.
Cuerpo de la interfaz
El interface_body de una interfaz define los miembros de la interfaz.
antlr
interface_body
Miembros de interfaz
Los miembros de una interfaz son los miembros heredados de las interfaces base y los
miembros declarados por la propia interfaz.
antlr
interface_member_declaration
: interface_method_declaration
| interface_property_declaration
| interface_event_declaration
| interface_indexer_declaration
Una declaración de interfaz puede declarar cero o más miembros. Los miembros de una
interfaz deben ser métodos, propiedades, eventos o indizadores. Una interfaz no puede
contener constantes, campos, operadores, constructores de instancias, destructores ni
tipos ni una interfaz puede contener miembros estáticos de cualquier tipo.
Todos los miembros de interfaz tienen acceso público de forma implícita. Se trata de un
error en tiempo de compilación para que las declaraciones de miembros de interfaz
incluyan modificadores. En concreto, los miembros de las interfaces no se pueden
declarar con los modificadores abstract ,, public protected , internal , private ,
virtual , override o static .
En el ejemplo
C#
declara una interfaz que contiene uno de los posibles tipos de miembros: un método,
una propiedad, un evento y un indexador.
Los miembros heredados de una interfaz no son específicamente parte del espacio de
declaración de la interfaz. Por lo tanto, una interfaz puede declarar un miembro con el
mismo nombre o signatura que un miembro heredado. Cuando esto ocurre, se dice que
el miembro de interfaz derivado oculta el miembro de interfaz base. Ocultar un
miembro heredado no se considera un error, pero hace que el compilador emita una
advertencia. Para suprimir la advertencia, la declaración del miembro de interfaz
derivado debe incluir un new modificador para indicar que el miembro derivado está
pensado para ocultar el miembro base. Este tema se describe con más detalle en
ocultarse a travésde la herencia.
Tenga en cuenta que los miembros de la clase object no son, estrictamente hablando,
miembros de cualquier interfaz (miembros de lainterfaz). Sin embargo, los miembros de
la clase object están disponibles a través de la búsqueda de miembros en cualquier
tipo de interfaz (búsqueda de miembros).
Métodos de interfaz
Los métodos de interfaz se declaran mediante interface_method_declaration s:
antlr
interface_method_declaration
Cada tipo de parámetro formal de un método de interfaz debe ser seguro para la
entrada (seguridad de lavarianza) y el tipo de valor devuelto debe ser void o ser seguro
para la salida. Además, cada restricción de tipo de clase, restricción de tipo de interfaz y
restricción de parámetro de tipo en cualquier parámetro de tipo del método debe ser
de seguridad de entrada.
C#
C#
class B {}
class D : B{}
class E : B {}
...
b.M<E>();
En realidad, se trata de una llamada a C.M<E> . Pero esa llamada requiere que E derive
de D , por lo que la seguridad de tipos se infringiría aquí.
Propiedades de la interfaz
Las propiedades de interfaz se declaran mediante interface_property_declaration s:
antlr
interface_property_declaration
interface_accessors
El tipo de una propiedad de interfaz debe ser seguro para la salida si hay un descriptor
de acceso get y debe ser seguro para la entrada si hay un descriptor de acceso set.
Eventos de interfaz
Los eventos de interfaz se declaran mediante interface_event_declaration s:
antlr
interface_event_declaration
antlr
interface_indexer_declaration
Todos los tipos de parámetros formales de un indizador de interfaz deben ser seguros
para la entrada. Además, cualquier out tipo de ref parámetro formal o también debe
ser seguro para la salida. Tenga en cuenta que incluso out se requiere que los
parámetros sean seguros para la entrada, debido a una limitación de la plataforma de
ejecución subyacente.
El tipo de un indizador de interfaz debe ser seguro para la salida si hay un descriptor de
acceso get y debe ser seguro para la entrada si hay un descriptor de acceso set.
Para las interfaces que son estrictamente herencia única (cada interfaz de la cadena de
herencia tiene exactamente cero o una interfaz base directa), los efectos de las reglas de
búsqueda de miembros (búsqueda de miembros), invocación de métodos (llamadas a
métodos) e indexador (acceso a indexador) son exactamente los mismos que para las
clases y los Structs: los miembros más derivados ocultan los miembros menos derivados
con el mismo nombre o signatura. Sin embargo, en el caso de las interfaces de herencia
múltiple, pueden producirse ambigüedades cuando dos o más interfaces base no
relacionadas declaran miembros con el mismo nombre o signatura. En esta sección se
muestran varios ejemplos de esas situaciones. En todos los casos, se pueden usar
conversiones explícitas para resolver las ambigüedades.
En el ejemplo
C#
interface IList
interface ICounter
class C
void Test(IListCounter x) {
x.Count(1); // Error
x.Count = 1; // Error
En el ejemplo
C#
interface IInteger
interface IDouble
class C
void Test(INumber n) {
En el ejemplo
C#
interface IBase
void G();
}
class A
void Test(IDerived d) {
C#
interface IControl
void Paint();
C#
namespace System
object Clone();
Implementaciones de interfaces
Las interfaces pueden ser implementadas por clases y Structs. Para indicar que una clase
o estructura implementa directamente una interfaz, el identificador de interfaz se incluye
en la lista de clases base de la clase o estructura. Por ejemplo:
C#
interface ICloneable
object Clone();
interface IComparable
Una clase o estructura que implementa directamente una interfaz también implementa
directamente todas las interfaces base de la interfaz de forma implícita. Esto es así
incluso si la clase o estructura no enumera explícitamente todas las interfaces base de la
lista de clases base. Por ejemplo:
C#
interface IControl
void Paint();
Cuando una clase C implementa directamente una interfaz, todas las clases derivadas
de C también implementan la interfaz implícitamente. Las interfaces base especificadas
en una declaración de clase pueden ser tipos de interfaz construidos (tipos construidos).
Una interfaz base no puede ser un parámetro de tipo por sí misma, aunque puede
incluir los parámetros de tipo que se encuentran en el ámbito. En el código siguiente se
muestra cómo una clase puede implementar y extender tipos construidos:
C#
class C<U,V> {}
interface I1<V> {}
Las interfaces base de una declaración de clase genérica deben cumplir la regla de
unicidad descrita en singularidad de las interfaces implementadas.
C#
interface IList<T>
T[] GetElements();
interface IDictionary<K,V>
V this[K key];
C#
interface IDisposable
void Dispose();
void IDisposable.Dispose() {
Close();
System.GC.SuppressFinalize(this);
Para que una implementación de miembro de interfaz explícita sea válida, la clase o
estructura debe nombrar una interfaz en su lista de clases base que contenga un
miembro cuyo nombre completo, tipo y tipos de parámetro coincidan exactamente con
los de la implementación explícita del miembro de interfaz. Por lo tanto, en la clase
siguiente
C#
C#
class Shape: ICloneable
C#
interface IControl
void Paint();
C#
interface I<T>
void F();
}
C#
x.F();
C#
interface I<T>
void F();
}
C#
x.F();
C#
interface I<A,B,C>
class C: I<object,C,string>
C#
class C: I<object,C,string>
...
void I<object,C,string>.H<T>(T t) {
string s = t; // Ok
H<T>(t);
Asignación de interfaz
Una clase o estructura debe proporcionar implementaciones de todos los miembros de
las interfaces que se enumeran en la lista de clases base de la clase o estructura. El
proceso de buscar implementaciones de miembros de interfaz en una clase o struct de
implementación se conoce como asignación de interfaz.
La asignación de interfaz para una clase o struct C localiza una implementación para
cada miembro de cada interfaz especificada en la lista de clases base de C . La
implementación de un miembro de interfaz determinado I.M , donde I es la interfaz en
la que M se declara el miembro, se determina examinando cada clase o struct S ,
empezando por C y repitiendo cada clase base sucesiva de C , hasta que se encuentre
una coincidencia:
Si S contiene una declaración de una implementación explícita de un miembro de
interfaz que coincide con I y M , este miembro es la implementación de I.M .
De lo contrario, si S contiene una declaración de un miembro público no estático
que coincide con M , este miembro es la implementación de I.M . Si hay más de
un miembro que coincide, no se especifica qué miembro es la implementación de
I.M . Esta situación solo puede producirse si S es un tipo construido en el que los
dos miembros declarados en el tipo genérico tienen firmas diferentes, pero los
argumentos de tipo hacen que sus firmas sean idénticas.
B son idénticos.
En el ejemplo
C#
interface ICloneable
object Clone();
class C: ICloneable
Si una clase o estructura implementa dos o más interfaces que contienen un miembro
con el mismo nombre, tipo y tipos de parámetro, es posible asignar cada uno de esos
miembros de interfaz a un único miembro de clase o de estructura. Por ejemplo
C#
interface IControl
void Paint();
interface IForm
void Paint();
En este caso, los Paint métodos de IControl y IForm se asignan al Paint método en
Page . También es posible tener implementaciones de miembros de interfaz explícitas
independientes para los dos métodos.
Si una clase o estructura implementa una interfaz que contiene miembros ocultos,
algunos miembros deben implementarse necesariamente mediante implementaciones
explícitas de miembros de interfaz. Por ejemplo
C#
interface IBase
int P { get; }
C#
class C: IDerived
class C: IDerived
class C: IDerived
Cuando una clase implementa varias interfaces que tienen la misma interfaz base, solo
puede haber una implementación de la interfaz base. En el ejemplo
C#
interface IControl
void Paint();
C#
interface Interface1
void F();
}
class Class1
Sin volver a implementar explícitamente una interfaz, una clase derivada no puede
alterar de ninguna manera las asignaciones de interfaz que hereda de sus clases base.
Por ejemplo, en las declaraciones
C#
interface IControl
void Paint();
C#
IControl ic = c;
IControl it = t;
Sin embargo, cuando un método de interfaz se asigna a un método virtual en una clase,
es posible que las clases derivadas invaliden el método virtual y modifiquen la
implementación de la interfaz. Por ejemplo, volver a escribir las declaraciones anteriores
en
C#
interface IControl
void Paint();
C#
IControl ic = c;
IControl it = t;
C#
interface IControl
void Paint();
Una nueva implementación de una interfaz sigue exactamente las mismas reglas de
asignación de interfaz que una implementación inicial de una interfaz. Por lo tanto, la
asignación de interfaz heredada no tiene ningún efecto en la asignación de interfaz
establecida para la reimplementación de la interfaz. Por ejemplo, en las declaraciones
C#
interface IControl
void Paint();
C#
interface IMethods
void F();
void G();
void H();
void I();
}
void IMethods.F() {}
void IMethods.G() {}
void IMethods.H() {}
Cuando una clase implementa una interfaz, también implementa implícitamente todas
las interfaces base de esa interfaz. Del mismo modo, una nueva implementación de una
interfaz también es implícitamente una reimplementación de todas las interfaces base
de la interfaz. Por ejemplo
C#
interface IBase
void F();
}
void G();
}
class C: IDerived
class D: C, IDerived
C#
interface IMethods
void F();
void G();
}
C#
interface IMethods
void F();
void G();
}
En el ejemplo
C#
enum Color
Red,
Green,
Blue
declara un tipo de enumeración denominado Color con miembros Red , Green y Blue .
Declaraciones de enumeración
Una declaración de enumeración declara un nuevo tipo de enumeración. Una
declaración de enumeración comienza con la palabra clave enum y define el nombre, la
accesibilidad, el tipo subyacente y los miembros de la enumeración.
antlr
enum_declaration
enum_base
: ':' integral_type
;
enum_body
En el ejemplo
C#
Red,
Green,
Blue
Modificadores de enumeración
Un enum_declaration puede incluir opcionalmente una secuencia de modificadores de
enumeración:
antlr
enum_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
antlr
enum_member_declarations
enum_member_declaration
C#
Red = -1,
Green = -2,
Blue = -3
C#
enum Color
Red,
Green,
Blue,
Max = Blue
En el ejemplo
C#
using System;
enum Color
Red,
Green = 10,
Blue
class Test
Console.WriteLine(StringFromColor(Color.Red));
Console.WriteLine(StringFromColor(Color.Green));
Console.WriteLine(StringFromColor(Color.Blue));
switch (c) {
case Color.Red:
case Color.Green:
case Color.Blue:
default:
Consola
Red = 0
Green = 10
Blue = 11
En el ejemplo
C#
enum Circular
A = B,
Tipo System.Enum
El tipo System.Enum es la clase base abstracta de todos los tipos de enumeración (es
distinto y diferente del tipo subyacente del tipo de enumeración) y los miembros
heredados de System.Enum están disponibles en cualquier tipo de enumeración. Existe
una conversión boxing (conversiones boxing) de cualquier tipo de enumeración a y
System.Enum existe una conversión unboxing (conversiones unboxing) de System.Enum a
Los delegados habilitan escenarios en los que otros lenguajes, como C++, Pascal y
modula, se han direccionado con punteros de función. A diferencia de los punteros de
función de C++, sin embargo, los delegados están totalmente orientados a objetos y, a
diferencia de los punteros de C++ a las funciones miembro, los delegados encapsulan
una instancia de objeto y un método.
Una declaración de delegado define una clase que se deriva de la clase System.Delegate
. Una instancia de delegado encapsula una lista de invocación, que es una lista de uno o
varios métodos, a la que se hace referencia como una entidad a la que se puede llamar.
En el caso de los métodos de instancia, una entidad a la que se puede llamar está
formada por una instancia y un método en esa instancia. En el caso de los métodos
estáticos, una entidad a la que se puede llamar consta simplemente de un método. Al
invocar una instancia de delegado con un conjunto de argumentos adecuado, se invoca
a cada una de las entidades a las que se puede llamar del delegado con el conjunto de
argumentos especificado.
Declaraciones de delegado
Un delegate_declaration es un type_declaration (declaraciones de tipos) que declara un
nuevo tipo de delegado.
antlr
delegate_declaration
identifier variant_type_parameter_list?
delegate_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| delegate_modifier_unsafe
El new modificador solo se permite en los delegados declarados dentro de otro tipo, en
cuyo caso especifica que dicho delegado oculte un miembro heredado con el mismo
nombre, tal y como se describe en el modificador New.
El tipo de valor devuelto de un tipo de delegado debe ser o tener seguridad void de
salida (seguridad de lavarianza).
Todos los tipos de parámetros formales de un tipo de delegado deben ser seguros para
la entrada. Además, los out ref tipos de parámetro o también deben ser de seguridad
de salida. Tenga en cuenta que incluso out se requiere que los parámetros sean seguros
para la entrada, debido a una limitación de la plataforma de ejecución subyacente.
Por ejemplo:
C#
class A
class B
Los métodos A.M1 y B.M1 son compatibles con los tipos de delegado D1 y D2 , ya que
tienen el mismo tipo de valor devuelto y la misma lista de parámetros; sin embargo,
estos tipos de delegado son dos tipos diferentes, por lo que no son intercambiables. Los
métodos B.M2 , B.M3 y B.M4 son incompatibles con los tipos de delegado D1 y D2 , ya
que tienen tipos de valor devuelto o listas de parámetros diferentes.
C#
class X
sino que es un tipo de clase del que se derivan todos los tipos de delegado.
C# proporciona una sintaxis especial para la invocación y creación de instancias de
delegado. A excepción de la creación de instancias, cualquier operación que se puede
aplicar a una instancia de clase o clase también se puede aplicar a una clase o instancia
de delegado, respectivamente. En concreto, es posible tener acceso a los miembros del
System.Delegate tipo a través de la sintaxis de acceso a miembros habitual.
C#
class C
class Test
Cuando cd1 cd2 se crean instancias de y, cada una de ellas encapsula un método.
Cuando cd3 se crea una instancia de, tiene una lista de invocaciones de dos métodos,
M1 y M2 , en ese orden. cd4 la lista de invocación de contiene M1 , M2 y M1 , en ese
Compatibilidad de delegado
Un método o un delegado M es compatible con un tipo D de delegado si se cumplen
todas las condiciones siguientes:
Para cada ref out parámetro o, el tipo de parámetro de D es el mismo que el tipo
de parámetro en M .
Existe una conversión de referencia implícita o de identidad del tipo de valor
devuelto de M al tipo de valor devuelto de D .
Por ejemplo:
C#
class C
class Test
C t = new C();
Una vez creada la instancia, las instancias de delegado siempre hacen referencia al
mismo objeto y método de destino. Recuerde que, cuando se combinan dos delegados
o uno se quita de otro, se produce un nuevo delegado que tiene su propia lista de
invocación. las listas de invocaciones de los delegados combinados o quitados
permanecen sin cambios.
Invocación de delegado
C# proporciona una sintaxis especial para invocar un delegado. Cuando se invoca una
instancia de delegado que no es null cuya lista de invocación contiene una entrada,
invoca el método con los mismos argumentos en los que se ha dado y devuelve el
mismo valor que el método al que se hace referencia. (Vea invocaciones de delegado
para obtener información detallada sobre la invocación de delegado). Si se produce una
excepción durante la invocación de este tipo de delegado y esa excepción no se detecta
dentro del método que se invocó, la búsqueda de una cláusula catch de excepción
continúa en el método que llamó al delegado, como si ese método hubiera llamado
directamente al método al que el delegado hizo referencia.
Al intentar invocar una instancia de delegado cuyo valor es null, se produce una
excepción de tipo System.NullReferenceException .
C#
using System;
class C
class Test
cd1(-1); // call M1
cd2(-2); // call M2
cd3 += cd1;
C c = new C();
cd3 += cd4;
cd3 -= cd4;
cd3 -= cd2;
cd3(60); // call M1
cd3(60); // call M1
Consola
C.M1: -1
C.M2: -2
C.M1: 10
C.M2: 10
C.M1: 20
C.M2: 20
C.M1: 20
C.M1: 30
C.M2: 30
C.M1: 30
C.M3: 30
C.M1: 40
C.M2: 40
C.M3: 40
C.M1: 50
C.M2: 50
C.M1: 60
C.M1: 60
Excepciones
Artículo • 16/09/2021 • Tiempo de lectura: 4 minutos
En C#, todas las excepciones deben estar representadas por una instancia de un
tipo de clase derivado de System.Exception . En C++, cualquier valor de cualquier
tipo se puede utilizar para representar una excepción.
En C#, se puede usar un bloque finally (la instrucción try) para escribir el código de
finalización que se ejecuta en la ejecución normal y en condiciones excepcionales.
Este código es difícil de escribir en C++ sin duplicar el código.
En C#, las excepciones de nivel de sistema, como Overflow, división por cero y
desreferencia nula, tienen clases de excepción bien definidas y se encuentran en
un par de condiciones de error de nivel de aplicación.
Una throw instrucción (la instrucción throw) produce una excepción de inmediato
y de forma incondicional. El control nunca alcanza la instrucción inmediatamente
después de throw .
Ciertas condiciones excepcionales que surgen durante el procesamiento de
instrucciones y expresiones de C# causan una excepción en determinadas
circunstancias en las que la operación no se puede completar con normalidad. Por
ejemplo, una operación de división de enteros (operador de división) produce una
excepción System.DivideByZeroException si el denominador es cero. Vea clases de
excepción comunes para obtener una lista de las distintas excepciones que pueden
producirse de esta manera.
La clase System.Exception
La System.Exception clase es el tipo base de todas las excepciones. Esta clase tiene
algunas propiedades importantes que comparten todas las excepciones:
Message es una propiedad de solo lectura de tipo string que contiene una
Cuando se produce una excepción, el sistema busca la cláusula más cercana catch que
pueda controlar la excepción, según lo determinado por el tipo en tiempo de ejecución
de la excepción. En primer lugar, se busca una instrucción de inclusión léxica en el
método actual try y las cláusulas Catch asociadas de la instrucción try se consideran en
orden. Si se produce un error, se busca en el método que llamó al método actual una
instrucción de inclusión léxica try que incluye el punto de la llamada al método actual.
Esta búsqueda continúa hasta que catch se encuentra una cláusula que puede controlar
la excepción actual, mediante el nombre de una clase de excepción que es de la misma
clase o una clase base, del tipo en tiempo de ejecución de la excepción que se está
iniciando. Una catch cláusula que no denomina una clase de excepción puede controlar
cualquier excepción.
Una vez que se encuentra una cláusula catch coincidente, el sistema se prepara para
transferir el control a la primera instrucción de la cláusula catch. Antes de que comience
la ejecución de la cláusula catch, el sistema ejecuta en primer lugar, en orden, cualquier
finally cláusula que se asociara con instrucciones try más anidada que la que capturó
la excepción.
originalmente.
Si la búsqueda de cláusulas Catch coincidentes alcanza el código que inició
inicialmente el subproceso, se termina la ejecución del subproceso. El impacto de
dicha terminación está definido por la implementación.
Clases de atributos
Una clase que deriva de la clase abstracta System.Attribute , ya sea directa o
indirectamente, es una clase * Attribute _. La declaración de una clase de atributo define
un nuevo tipo de _ Attribute* que se puede colocar en una declaración. Por Convención,
las clases de atributo se denominan con el sufijo Attribute . Los usos de un atributo
pueden incluir u omitir este sufijo.
Uso de atributos
El atributo AttributeUsage (el atributo AttributeUsage) se usa para describir cómo se
puede utilizar una clase de atributo.
que permite que una clase de atributo especifique los tipos de declaraciones en las que
se puede usar. En el ejemplo
C#
using System;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
...
define una clase de atributo denominada SimpleAttribute que se puede colocar solo en
class_declaration s y interface_declaration s. En el ejemplo
C#
muestra varios usos del Simple atributo. Aunque este atributo se define con el nombre
SimpleAttribute , cuando se utiliza este atributo, Attribute se puede omitir el sufijo, lo
que da lugar a un nombre corto Simple . Por lo tanto, el ejemplo anterior es
semánticamente equivalente a lo siguiente:
C#
En el ejemplo
C#
using System;
this.name = name;
C#
class Class1
...
muestra una declaración de clase con dos usos del Author atributo.
AttributeUsage tiene otro parámetro con nombre llamado Inherited , que indica si el
atributo, cuando se especifica en una clase base, también es heredado por las clases que
derivan de esa clase base. Si Inherited para una clase de atributo es true, se hereda ese
atributo. Si Inherited para una clase de atributo es false, ese atributo no se hereda. Si
no se especifica, su valor predeterminado es true.
C#
using System;
es equivalente a lo siguiente:
C#
using System;
[AttributeUsage(
AttributeTargets.All,
AllowMultiple = false,
Inherited = true)
En el ejemplo
C#
using System;
[AttributeUsage(AttributeTargets.Class)]
...
get {...}
set {...}
get {...}
C#
[Help("http://www.mycompany.com/.../Class1.htm")]
class Class1
...
class Class2
...
Uno de los tipos siguientes: bool , byte , char , double , float , int , long ,
sbyte , short , string , uint , ulong , ushort .
El tipo object .
El tipo System.Type .
Un tipo de enumeración, siempre que tenga accesibilidad pública y los tipos en los
que está anidado (si los hubiera) también tengan accesibilidad pública
(especificación de atributo).
Matrices unidimensionales de los tipos anteriores.
Un argumento de constructor o un campo público que no tiene uno de estos
tipos, no se puede usar como un parámetro posicional o con nombre en una
especificación de atributo.
Especificación de atributo
*Especificación de atributo _ es la aplicación de un atributo definido previamente a una
declaración. Un atributo es un fragmento de información declarativa adicional que se
especifica para una declaración. Los atributos se pueden especificar en el ámbito global
(para especificar atributos en el ensamblado o módulo contenedor) y para
_type_declaration * s (declaraciones de tipos). class_member_declaration s (restricciones
de parámetros de tipo), interface_member_declaration s (miembros de interfaz),
struct_member_declaration s (miembros de struct), enum_member_declaration s
(miembros de enumeración), accessor_declarations (descriptores de acceso),
event_accessor_declarations (eventos similares a campos) y formal_parameter_list s
(parámetros de método).
antlr
global_attributes
: global_attribute_section+
global_attribute_section
global_attribute_target_specifier
: global_attribute_target ':'
global_attribute_target
: 'assembly'
| 'module'
attributes
: attribute_section+
attribute_section
attribute_target_specifier
: attribute_target ':'
attribute_target
: 'field'
| 'event'
| 'method'
| 'param'
| 'property'
| 'return'
| 'type'
attribute_list
attribute
: attribute_name attribute_arguments?
attribute_name
: type_name
attribute_arguments
positional_argument_list
positional_argument
: attribute_argument_expression
named_argument_list
named_argument
attribute_argument_expression
: expression
C#
class Class1 {}
produce un error en tiempo de compilación porque intenta utilizar Class1 como una
clase de atributos cuando Class1 no es una clase de atributos.
C#
class Class1 {}
[Author("Dennis Ritchie")]
class Class2 {}
C#
[param: Author("Brian Kernighan")] // Error
class Class1 {}
C#
using System;
[AttributeUsage(AttributeTargets.All)]
{}
[AttributeUsage(AttributeTargets.All)]
{}
class Class1 {}
class Class2 {}
[@X] // Refers to X
class Class3 {}
class Class4 {}
C#
using System;
[AttributeUsage(AttributeTargets.All)]
{}
class Class1 {}
class Class2 {}
class Class3 {}
Es un error en tiempo de compilación utilizar una clase de atributo de un solo uso más
de una vez en la misma entidad. En el ejemplo
C#
using System;
[AttributeUsage(AttributeTargets.Class)]
string value;
this.value = value;
get {...}
[HelpString("Description of Class1")]
Por ejemplo:
C#
using System;
[AttributeUsage(AttributeTargets.Class)]
public int P1 {
get {...}
set {...}
public Type P2 {
get {...}
set {...}
public object P3 {
get {...}
set {...}
class MyClass {}
C#
class A: Attribute
class G<T>
class X
Instancias de atributos
Una instancia de atributo es una instancia de que representa un atributo en tiempo de
ejecución. Un atributo se define con una clase de atributo, argumentos posicionales y
argumentos con nombre. Una instancia de atributo es una instancia de la clase de
atributos que se inicializa con los argumentos posicionales y con nombre.
Compilación de un atributo
La compilación de un atributo con la clase de atributo T , positional_argument_list P y
named_argument_list N , consta de los siguientes pasos:
Atributos reservados
Un pequeño número de atributos afecta al lenguaje de alguna manera. Estos atributos
incluyen lo siguiente:
System.Runtime.CompilerServices.CallerFilePathAttribute y
System.Runtime.CompilerServices.CallerMemberNameAttribute (atributos de
El atributo AttributeUsage
El atributo AttributeUsage se utiliza para describir la forma en que se puede usar la
clase de atributo.
Una clase que se decora con el AttributeUsage atributo debe derivarse de
System.Attribute , ya sea directa o indirectamente. De lo contrario, se produce un error
en tiempo de compilación.
C#
namespace System
[AttributeUsage(AttributeTargets.Class)]
Assembly = 0x0001,
Module = 0x0002,
Class = 0x0004,
Struct = 0x0008,
Enum = 0x0010,
Constructor = 0x0020,
Method = 0x0040,
Property = 0x0080,
Field = 0x0100,
Event = 0x0200,
Interface = 0x0400,
Parameter = 0x0800,
Delegate = 0x1000,
ReturnValue = 0x2000,
Delegate | ReturnValue
El atributo Conditional
El atributo Conditional habilita la definición de *métodos condicionales _ y _ clases de
atributos condicionales *.
C#
namespace System.Diagnostics
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class,
AllowMultiple = true)]
Métodos condicionales
Un método decorado con el Conditional atributo es un método condicional. El
Conditional atributo indica una condición mediante la prueba de un símbolo de
C#
#define DEBUG
using System;
using System.Diagnostics;
class Class1
[Conditional("DEBUG")]
Console.WriteLine("Executed Class1.M");
class Class2
Class1.M();
Archivo class1.cs :
C#
using System.Diagnostics;
class Class1
[Conditional("DEBUG")]
Console.WriteLine("Executed Class1.F");
Archivo class2.cs :
C#
#define DEBUG
class Class2
Class1.F(); // F is called
Archivo class3.cs :
C#
#undef DEBUG
class Class3
las clases Class2 y Class3 cada una contienen llamadas al método condicional
Class1.F , que es condicional en función de si se ha definido o no DEBUG . Puesto que
El uso de métodos condicionales en una cadena de herencia puede ser confuso. Las
llamadas realizadas a un método condicional a través base de, del formulario base.M ,
están sujetas a las reglas de llamada de método condicional normales. En el ejemplo
Archivo class1.cs :
C#
using System;
using System.Diagnostics;
class Class1
[Conditional("DEBUG")]
Console.WriteLine("Class1.M executed");
Archivo class2.cs :
C#
using System;
Console.WriteLine("Class2.M executed");
Archivo class3.cs :
C#
#define DEBUG
using System;
class Class3
c.M(); // M is called
Class2 incluye una llamada al M definido en su clase base. Esta llamada se omite
C#
using System;
using System.Diagnostics;
[Conditional("ALPHA")]
[Conditional("BETA")]
declara TestAttribute como una clase de atributo condicional asociada a los símbolos
de compilaciones condicionales ALPHA y BETA .
Archivo test.cs :
C#
using System;
using System.Diagnostics;
[Conditional("DEBUG")]
Archivo class1.cs :
C#
#define DEBUG
class Class1 {}
Archivo class2.cs :
C#
#undef DEBUG
class Class2 {}
cada una de las clases Class1 y Class2 se decoran con el atributo Test , que es
condicional en función de si se ha definido o no DEBUG . Puesto que este símbolo se
define en el contexto de Class1 pero no Class2 , se incluye la especificación del Test
atributo en Class1 , mientras que la especificación del Test atributo en Class2 se
omite.
El atributo Obsolete
El atributo Obsolete se usa para marcar tipos y miembros de tipos que ya no se deben
usar.
C#
namespace System
[AttributeUsage(
AttributeTargets.Class |
AttributeTargets.Struct |
AttributeTargets.Enum |
AttributeTargets.Interface |
AttributeTargets.Delegate |
AttributeTargets.Method |
AttributeTargets.Constructor |
AttributeTargets.Property |
AttributeTargets.Field |
AttributeTargets.Event,
Inherited = false)
En el ejemplo
C#
class A
class B
class Test
a.F();
la clase A se decora con el Obsolete atributo. Cada uso de A en Main genera una
advertencia que incluye el mensaje especificado, "esta clase está obsoleta; Use la clase B
en su lugar ".
Cuando un parámetro opcional se anota con uno de los atributos de información del
llamador, la omisión del argumento correspondiente en una llamada no hace que el
valor del parámetro predeterminado se sustituya. En su lugar, si la información
especificada sobre el contexto de la llamada está disponible, esa información se pasará
como el valor del argumento.
Por ejemplo:
C#
using System.Runtime.CompilerServices
...
Una llamada a Log() sin argumentos imprimiría el número de línea y la ruta de acceso
del archivo de la llamada, así como el nombre del miembro en el que se produjo la
llamada.
Los atributos de información del llamador se pueden producir en parámetros opcionales
en cualquier parte, incluidas en las declaraciones de delegado. Sin embargo, los
atributos de información del llamador específico tienen restricciones en los tipos de los
parámetros que pueden atribuir, de modo que siempre habrá una conversión implícita
de un valor sustituido al tipo de parámetro.
El atributo CallerLineNumber
System.Runtime.CompilerServices.CallerLineNumberAttribute Se permite en parámetros
Tenga en cuenta que el número de línea puede verse afectado por las #line directivas
(directivas de línea).
El atributo CallerFilePath
Tenga en cuenta que la ruta de acceso al archivo puede verse afectada por las #line
directivas (directivas de línea).
El atributo CallerMemberName
System.Runtime.CompilerServices.CallerMemberNameAttribute Se permite en parámetros
Si una invocación de función desde una ubicación dentro del cuerpo de un miembro de
función o dentro de un atributo aplicado al propio miembro de la función o a su tipo de
valor devuelto, parámetros o parámetros de tipo en el código fuente omite un
parámetro opcional con CallerMemberNameAttribute , se usa un literal de cadena que
representa el nombre de ese miembro como argumento para la invocación en lugar del
valor del parámetro predeterminado
En el caso de las invocaciones que se producen dentro de los descriptores de acceso del
indizador, el nombre de miembro utilizado es el proporcionado por un
IndexerNameAttribute (el atributo IndexerName) en el miembro del indexador, si está
presente, o el nombre predeterminado Item en caso contrario.
El atributo IndexerName
Los indizadores se implementan en .NET mediante propiedades indizadas y tienen un
nombre en los metadatos de .NET. Si no hay ningún IndexerName atributo presente para
un indexador, se usa el nombre de Item forma predeterminada. El IndexerName atributo
permite a un desarrollador reemplazar este valor predeterminado y especificar otro
nombre.
C#
namespace System.Runtime.CompilerServices.CSharp
[AttributeUsage(AttributeTargets.Property)]
Código no seguro
Artículo • 16/09/2021 • Tiempo de lectura: 37 minutos
Contextos no seguros
Las características no seguras de C# solo están disponibles en contextos no seguros. Un
contexto no seguro se introduce mediante la inclusión de un unsafe modificador en la
declaración de un tipo o miembro, o mediante la utilización de un unsafe_statement:
Una declaración de una clase, un struct, una interfaz o un delegado puede incluir
un unsafe modificador, en cuyo caso toda la extensión textual de esa declaración
de tipos (incluido el cuerpo de la clase, el struct o la interfaz) se considera un
contexto no seguro.
Una declaración de un campo, un método, una propiedad, un evento, un
indexador, un operador, un constructor de instancia, un destructor o un
constructor estático puede incluir un unsafe modificador, en cuyo caso toda la
extensión textual de dicha declaración de miembro se considera un contexto no
seguro.
Un unsafe_statement habilita el uso de un contexto no seguro dentro de un bloque.
Toda la extensión textual del bloque asociado se considera un contexto no seguro.
antlr
class_modifier_unsafe
: 'unsafe'
struct_modifier_unsafe
: 'unsafe'
interface_modifier_unsafe
: 'unsafe'
delegate_modifier_unsafe
: 'unsafe'
field_modifier_unsafe
: 'unsafe'
method_modifier_unsafe
: 'unsafe'
property_modifier_unsafe
: 'unsafe'
event_modifier_unsafe
: 'unsafe'
indexer_modifier_unsafe
: 'unsafe'
operator_modifier_unsafe
: 'unsafe'
constructor_modifier_unsafe
: 'unsafe'
destructor_declaration_unsafe
static_constructor_modifiers_unsafe
embedded_statement_unsafe
: unsafe_statement
| fixed_statement
unsafe_statement
: 'unsafe' block
En el ejemplo
C#
C#
Aquí, los unsafe modificadores de las declaraciones de campo hacen que esas
declaraciones se consideren contextos no seguros.
C#
public class A
char* p;
...
public class B: A
base.F();
...
C#
public class B: A
Aquí, dado F que la firma de incluye un tipo de puntero, solo se puede escribir en un
contexto no seguro. Sin embargo, el contexto no seguro se puede introducir haciendo
que la clase completa sea insegura, como es el caso en A , o incluyendo un unsafe
modificador en la declaración del método, como es el caso B de.
Tipos de puntero
En un contexto no seguro, un tipo (tipos) puede ser un pointer_type , así como un
value_type o un reference_type. Sin embargo, un pointer_type también puede usarse en
una typeof expresión (expresiones de creación de objetos anónimos) fuera de un
contexto no seguro, ya que dicho uso no es seguro.
antlr
type_unsafe
: pointer_type
antlr
pointer_type
: unmanaged_type '*'
| 'void' '*'
unmanaged_type
: type
sbyte , byte , short , ushort , int , uint , long , ulong , char , float , double ,
decimal o bool .
Cualquier enum_type.
Cualquier pointer_type.
Cualquier struct_type definido por el usuario que no sea un tipo construido y que
contenga campos de unmanaged_type s solamente.
Ejemplo Descripción
Para una implementación determinada, todos los tipos de puntero deben tener el
mismo tamaño y representación.
C#
Al igual que una referencia de objeto, un puntero puede ser null . Al aplicar el
operador de direccionamiento indirecto a un null puntero, se produce un
comportamiento definido por la implementación. Un puntero con valor null se
representa mediante All-bits-cero.
El void* tipo representa un puntero a un tipo desconocido. Dado que el tipo referente
es desconocido, el operador de direccionamiento indirecto no se puede aplicar a un
puntero de tipo void* , ni se puede realizar ninguna operación aritmética en dicho
puntero. Sin embargo, un puntero de tipo void* se puede convertir a cualquier otro
tipo de puntero (y viceversa).
Los tipos de puntero son una categoría independiente de tipos. A diferencia de los tipos
de referencia y los tipos de valor, los tipos de puntero no heredan de object y no
existen conversiones entre tipos de puntero y object . En concreto, no se admiten las
conversiones boxing y unboxing (conversión boxing y unboxing) para los punteros. Sin
embargo, se permiten conversiones entre diferentes tipos de puntero y entre tipos de
puntero y tipos enteros. Esto se describe en conversiones de puntero.
Aunque los punteros se pueden pasar ref como out parámetros o, esto puede
provocar un comportamiento indefinido, ya que el puntero se puede establecer para
que apunte a una variable local que ya no existe cuando el método llamado vuelve, o al
objeto fijo al que se ha usado para apuntar, ya no se ha corregido. Por ejemplo:
C#
using System;
class Test
int i = 10;
pi1 = &i;
// ...
pi2 = pj;
int i = 10;
unsafe {
int* px1;
Un método puede devolver un valor de algún tipo y ese tipo puede ser un puntero. Por
ejemplo, cuando se proporciona un puntero a una secuencia contigua de int s, el
recuento de elementos de la secuencia y otro int valor, el método siguiente devuelve la
dirección de ese valor en esa secuencia, si se produce una coincidencia; de lo contrario,
devuelve null :
C#
if (*pi == value)
return pi;
++pi;
return null;
El & operador (el operador Address-of) permite obtener la dirección de una variable fija
sin restricciones. Sin embargo, dado que una variable móvil está sujeta a la reubicación
o eliminación por parte del recolector de elementos no utilizados, la dirección de una
variable móvil solo se puede obtener mediante una fixed instrucción (la instrucción
Fixed) y esa dirección permanece válida solo mientras dure la fixed instrucción.
Tenga en cuenta que un campo estático se clasifica como una variable móvil. Tenga en
cuenta también que un ref out parámetro o se clasifica como una variable móvil,
incluso si el argumento especificado para el parámetro es una variable fija. Por último,
tenga en cuenta que una variable generada al desreferenciar un puntero siempre se
clasifica como una variable fija.
Conversiones de puntero
En un contexto no seguro, el conjunto de conversiones implícitas disponibles
(conversiones implícitas) se extiende para incluir las siguientes conversiones de puntero
implícitas:
Las conversiones entre dos tipos de puntero nunca cambian el valor del puntero real. En
otras palabras, una conversión de un tipo de puntero a otro no tiene ningún efecto en la
dirección subyacente proporcionada por el puntero.
Considere el siguiente caso en el que se tiene acceso a una variable con un tipo a través
de un puntero a un tipo diferente:
C#
char c = 'A';
char* pc = &c;
void* pv = pc;
int* pi = (int*)pv;
C#
using System;
class Test
double d = 123.456e23;
unsafe {
byte* pb = (byte*)&d;
Console.WriteLine();
Las asignaciones entre punteros y enteros están definidas por la implementación. Sin
embargo, en las arquitecturas de CPU de 32 * y 64 bits con un espacio de direcciones
lineal, las conversiones de punteros a o desde tipos enteros normalmente se comportan
exactamente igual que las conversiones de uint ulong los valores o, respectivamente, a
o desde esos tipos enteros.
Matrices de puntero
En un contexto no seguro, se pueden construir matrices de punteros. En las matrices de
puntero solo se permiten algunas de las conversiones que se aplican a otros tipos de
matriz:
unidimensional T[] nunca se aplican a las matrices de puntero, ya que los tipos de
puntero no se pueden usar como argumentos de tipo y no hay conversiones de
tipos de puntero en tipos que no son de puntero.
C#
foreach (V v in x) embedded_statement