stringtranslate.com

Sintaxis de C Sharp

En este artículo se describe la sintaxis del lenguaje de programación C# . Las características descritas son compatibles con .NET Framework y Mono .

Lo esencial

Identificador

Un identificador es el nombre de un elemento en el código . Puede contener letras, dígitos y guiones bajos ( _), y distingue entre mayúsculas y minúsculas ( FOOes diferente de foo). El lenguaje impone las siguientes restricciones a los nombres de identificadores:

Los nombres de identificadores pueden tener como prefijo un signo arroba ( @), pero esto es insignificante; @namees el mismo identificador que name.

Microsoft ha publicado convenciones de nomenclatura para identificadores en C#, que recomiendan el uso de PascalCase para los nombres de tipos y la mayoría de los miembros de tipos, y camelCase para variables y para campos privados o internos. [1] Sin embargo, estas convenciones de nomenclatura no se aplican en el lenguaje.

Palabras clave

Las palabras clave son palabras reservadas predefinidas con un significado sintáctico especial. [2] El lenguaje tiene dos tipos de palabras clave: contextuales y reservadas. Las palabras clave reservadas, como falseo, bytesolo se pueden usar como palabras clave. Las palabras clave contextuales, como whereo, fromsolo se tratan como palabras clave en determinadas situaciones. [3] Si se necesita un identificador que sea el mismo que una palabra clave reservada, se puede anteponer un signo arroba para distinguirlo. Por ejemplo, @outse interpreta como un identificador, mientras que outcomo una palabra clave. Esta sintaxis facilita la reutilización del código .NET escrito en otros lenguajes. [4]

Las siguientes palabras clave de C# son palabras reservadas: [2]

Una palabra clave contextual se utiliza para proporcionar un significado específico en el código, pero no es una palabra reservada en C#. Algunas palabras clave contextuales, como partialy where, tienen significados especiales en varios contextos. Las siguientes palabras clave de C# son contextuales: [5]

Literales

  1. ^ Las cadenas no terminan en nulo en C#, por lo que pueden aparecer caracteres nulos en cualquier parte de una cadena.

Separadores de dígitos

A partir de C# 7.0, el símbolo de guión bajo se puede utilizar para separar dígitos en valores numéricos con fines de legibilidad. El compilador ignora estos guiones bajos.

int bin = 0 b1101_0010_1011_0100 ; int hexadecimal = 0 x2F_BB_4A_F1 ; int dec = 1 _000_500_954 ; doble real = 1 _500 . 200 _2e - 1 _000 ;            

Generalmente, se puede colocar solo entre caracteres numéricos. No se puede colocar al principio ( _121) ni al final del valor ( 121_o 121.05_), junto al decimal en valores de punto flotante ( 10_.0), junto al carácter de exponente ( 1.1e_1) ni junto al especificador de tipo ( 10_f).

Variables

Las variables son identificadores asociados a valores. Se declaran escribiendo el tipo y el nombre de la variable y, opcionalmente, se inicializan en la misma declaración.

Declarar

int myInt ; // Declarar una variable no inicializada llamada 'myInt', de tipo 'int'  

Asignación

int myInt ; // Declarar una variable no inicializada myInt = 35 ; // Asignar un valor a la variable     

Inicializar

int myInt = 35 ; // Declarar e inicializar la variable    

Se pueden declarar e inicializar múltiples variables del mismo tipo en una sola declaración.

int a , b ; // Declarar múltiples variables del mismo tipo   int a = 2 , b = 3 ; // Declarar e inicializar múltiples variables del mismo tipo       

Inferencia de tipo de variable local

Esta es una característica de C# 3.0 .

C# 3.0 introdujo la inferencia de tipos, lo que permite reemplazar el especificador de tipo de una declaración de variable por la palabra clave var, si su tipo real se puede determinar estáticamente a partir del inicializador. Esto reduce la repetición, especialmente para tipos con múltiples parámetros de tipo genéricos, y se adhiere más al principio DRY .

var myChars = nuevo carácter [] { 'A' , 'Ö' }; // o char[] myChars = new char[] {'A', 'Ö'};       var myNums = nueva Lista < int > (); // o Lista<int> myNums = nueva Lista<int>();     

Constantes

Las constantes son valores inmutables.

const

Al declarar una variable local o un campo con la constpalabra clave como prefijo, el valor debe indicarse en el momento de la declaración. Después de eso, se bloquea y no puede cambiar. Se pueden declarar en el contexto como un campo o una variable local. Las constantes son implícitamente estáticas.

constante doble PI = 3,14 ;    

Esto muestra ambos usos de la palabra clave.

clase pública Foo { const privada doble X = 3 ;         público Foo () { constante int y = 2 ; } }        

readonly

La readonlypalabra clave hace algo similar con los campos. Como los campos marcados, constno pueden cambiar una vez inicializados. La diferencia es que uno puede elegir inicializarlos en un constructor o con un valor que no se conoce hasta el momento de la ejecución. Esto solo funciona con los campos. readonlyLos campos pueden ser miembros de una instancia o miembros de una clase estática.

Bloques de código

Las llaves se utilizan para indicar un bloque de código y un nuevo ámbito . Los miembros de clase y el cuerpo de un método son ejemplos de lo que puede estar dentro de estas llaves en varios contextos.{ ... }

Dentro de los cuerpos de los métodos, se pueden utilizar llaves para crear nuevos ámbitos:

vacío HacerAlgo () { int a ;    { entero b ; a = 1 ; }       a = 2 ; b = 3 ; // Fallará porque la variable está declarada en un ámbito interno. }      

Estructura del programa

La aplicación AC# consta de clases y sus miembros. Las clases y otros tipos existen en espacios de nombres, pero también pueden estar anidados dentro de otras clases.

Método principal

Ya sea una consola o una aplicación de interfaz gráfica, el programa debe tener algún tipo de punto de entrada. El punto de entrada de la aplicación C# es el método llamado Main. Solo puede haber uno y es un método estático en una clase. El método generalmente devuelve voidy recibe argumentos de línea de comandos como una matriz de cadenas.

static void Main ( string [] args ) { } // OR El método Main se puede definir sin parámetros. static void Main () { }     

También se permite que el método principal devuelva un valor entero si se especifica.

int estático Main ( cadena [] args ) { devolver 0 ; }     

Principal asíncrono

Esta es una característica de C# 7.1.

Se pueden esperar tareas asincrónicas en el Mainmétodo declarándolo como de tipo de retorno Task.

Tarea asincrónica estática Principal ( cadena [] argumentos ) { await DoWorkAsync ( 42 ); }      

Se admiten todas las combinaciones de Task, o Task<int>,y con o sin el parámetro.string[] args

Declaraciones de alto nivel

Esta es una característica de C# 9.0.

De manera similar a lo que ocurre en los lenguajes de script, las declaraciones de nivel superior eliminan la ceremonia de tener que declarar la Programclase con un Mainmétodo.

En cambio, las instrucciones se pueden escribir directamente en un archivo específico, y ese archivo será el punto de entrada del programa. El código en otros archivos todavía tendrá que definirse en clases.

Esto se introdujo para hacer que C# fuera menos detallado y, por lo tanto, más accesible para que los principiantes pudieran comenzar.

usando Sistema ; Consola .WriteLine ( "¡Hola mundo!" ) ;

Los tipos se declaran después de las declaraciones y estarán disponibles automáticamente en las declaraciones anteriores.

Espacios de nombres

Los espacios de nombres son parte de un nombre de tipo y se utilizan para agrupar y/o distinguir entidades nombradas de otras.

System . IO . DirectoryInfo // DirectoryInfo está en el espacio de nombres System.IO 

Un espacio de nombres se define así:

espacio de nombres FooNamespace { // Miembros }  

Un espacio de nombres se puede colocar dentro de otro espacio de nombres de la siguiente manera:

espacio de nombres FooNamespace { // Miembros espacio de nombres BarNamespace { // Miembros } }       

También se puede hacer así:

espacio de nombres FooNamspace { // Miembros }  espacio de nombres FooNamspace.BarNamespace { // Miembros }  

usingdirectiva

La usingdirectiva carga un espacio de nombres específico de un ensamblado referenciado. Normalmente se ubica en la parte superior (o encabezado) de un archivo de código, pero se puede ubicar en otro lugar si se desea, por ejemplo, dentro de las clases. [ cita requerida ]

usando Sistema ; usando System.Collections ;  

La directiva también se puede utilizar para definir otro nombre para un espacio de nombres o tipo existente. Esto a veces resulta útil cuando los nombres son demasiado largos y difíciles de leer.

utilizando Net = System . Net ; utilizando DirInfo = System . IO . DirectoryInfo ;      

using staticdirectiva

La directiva carga los miembros estáticos de un tipo especificado en el ámbito actual, haciéndolos accesibles directamente por el nombre del miembro.using static

utilizando el sistema estático.Consola ;  WriteLine ( "¡Hola, mundo!" );

Operadores

Sobrecarga del operador

Algunos de los operadores existentes se pueden sobrecargar escribiendo un método de sobrecarga.

operador público estático Foo + ( Foo foo , Bar bar ) { devuelve nuevo Foo ( foo . Valor + bar . Valor ); }           

Estos son los operadores sobrecargables :

Operadores de conversión

El operador de conversión no se puede sobrecargar, pero se puede escribir un método de operador de conversión que se encuentre en la clase de destino. Los métodos de conversión pueden definir dos variedades de operadores: operadores de conversión implícitos y explícitos. El operador implícito convertirá sin especificar el operador de conversión ( ) y el operador explícito requiere que se lo utilice.( )

Operador de conversión implícito

clase Foo { público int Valor ;     operador implícito estático público Foo ( int valor ) { return new Foo ( valor ); } } // Conversión implícita Foo foo = 2 ;             

Operador de conversión explícita

clase Foo { público int Valor ;     operador público estático explícito Foo ( int valor ) { return new Foo ( valor ); } } // Conversión explícita Foo foo = ( Foo ) 2 ;             

asoperador

El asoperador intentará realizar una conversión silenciosa a un tipo determinado. Devolverá el objeto como el nuevo tipo si es posible y, de lo contrario, devolverá un valor nulo.

Flujo de datos stream = Archivo . Abrir ( @"C:\Temp\data.dat" ); FileStream fstream = stream como FileStream ; // Devolverá un objeto.         Cadena str = stream como String ; // Devolverá nulo.      

Operador de coalescencia nula

Esta es una característica de C# 2.0 .

La siguiente:

devolver ifNotNullValue ?? de lo contrarioValue ;   

es una abreviatura de:

devolver ifNotNullValue != null ? ifNotNullValue : de lo contrarioValue ;       

Lo que significa que si el contenido de la variable ifNotNullValueno es nulo, se devolverá ese contenido; de lo contrario, otherwiseValuese devolverá el contenido de la variable.

C# 8.0 introduce la asignación de coalescencia nula , de modo que

variable ??= elseValue ;  

es equivalente a

si ( variable es nula ) variable = elseValue ;      

Estructuras de control

C# hereda la mayoría de las estructuras de control de C/C++ y también agrega otras nuevas, como la foreachdeclaración.

Estructuras condicionales

Estas estructuras controlan el flujo del programa a través de condiciones dadas.

ifdeclaración

La ifdeclaración se introduce cuando la condición dada es verdadera. Las declaraciones de caso de una sola línea no requieren llaves de bloque, aunque por convención se prefiere en la mayoría de los casos.

Declaración simple de una línea:

si ( i == 3 ) ... ;     

Multilínea con bloque else (sin llaves):

si ( i == 2 ) ... de lo contrario ...     

Convenciones de codificación recomendadas para una declaración if.

si ( i == 3 ) { ... } de lo contrario si ( i == 2 ) { ... } de lo contrario { ... }          

switchdeclaración

El switchconstructo sirve como filtro para diferentes valores. Cada valor conduce a un "caso". No se permite pasar de una sección de caso a otra y, por lo tanto, la palabra clave breakse utiliza normalmente para finalizar un caso. returnTambién se puede utilizar un incondicional en una sección de caso para finalizar un caso. Vea también cómo gotose puede utilizar una declaración para pasar de un caso a otro. Sin embargo, muchos casos pueden conducir al mismo código. El caso predeterminado maneja todos los demás casos que no maneja el constructo.

switch ( ch ) { case 'A' : declaración ; ... break ; case 'B' : declaración ; break ; case 'C' : // Una sección de conmutación puede tener múltiples etiquetas de caso. case 'D' : ... break ; predeterminado : ... break ; }                    

Estructuras de iteración

Las declaraciones de iteración son declaraciones que se ejecutan repetidamente cuando una condición determinada se evalúa como verdadera.

whilebucle

mientras ( i == verdadero ) { ... }    

do ... whilebucle

hacer {} mientras ( i == verdadero );   

forbucle

El forbucle consta de tres partes: declaración , condición y contraexpresión . Se puede omitir cualquiera de ellas, ya que son opcionales.

para ( int i = 0 ; i < 10 ; i ++ ) { ... }         

Es equivalente a este código representado con una whiledeclaración, excepto que aquí la ivariable no es local al bucle.

int i = 0 ; mientras ( i < 10 ) { //... i ++ ; }        

foreachbucle

La foreachdeclaración se deriva de la fordeclaración y hace uso de un patrón determinado descrito en la especificación del lenguaje C# para obtener y utilizar un enumerador de elementos sobre los cuales iterar.

Cada elemento de la colección dada se devolverá y se podrá acceder a él en el contexto del bloque de código. Cuando se haya ejecutado el bloque, se devolverá el siguiente elemento hasta que no queden elementos.

foreach ( int i en intList ) { ... }     

Sentencias de salto

Las instrucciones de salto se heredan de C/C++ y, en última instancia, de los lenguajes ensambladores a través de él. Simplemente representan las instrucciones de salto de un lenguaje ensamblador que controla el flujo de un programa.

Etiquetas y gotodeclaraciones

Las etiquetas son puntos asignados en el código a los que se puede acceder mediante el uso de la gotodeclaración.

inicio : ....... ir a inicio ;   

Tenga en cuenta que no es necesario colocar la etiqueta después de la gotodeclaración; puede estar antes de ella en el archivo fuente.

La gotodeclaración se puede utilizar en switchdeclaraciones para saltar de un caso a otro o para pasar de un caso al siguiente.

switch ( n ) { case 1 : Console . WriteLine ( "Caso 1" ); break ; case 2 : Console . WriteLine ( "Caso 2" ); goto case 1 ; case 3 : Console . WriteLine ( "Caso 3" ); case 4 : // La compilación fallará aquí ya que los casos no pueden pasar al siguiente caso en C#. Console . WriteLine ( "Caso 4" ); goto default ; // Esta es la forma correcta de pasar al siguiente caso. case 5 : // Múltiples etiquetas para el mismo código están bien case 6 : default : Console . WriteLine ( "Default" ); break ; // Incluso el valor predeterminado no debe alcanzar el punto final }                              

breakdeclaración

La breakinstrucción sale del bucle o switchinstrucción más cercana. La ejecución continúa en la instrucción posterior a la instrucción finalizada, si la hubiera.

int e = 10 ; for ( int i = 0 ; i < e ; i ++ ) { while ( true ) { break ; } // Se romperá hasta este punto. }                 

continuedeclaración

La continuedeclaración interrumpe la iteración actual de la declaración de control actual y comienza la siguiente iteración.

int ch ; while (( ch = Console . Read ()) != - 1 ) { if ( ch == ' ' ) continue ; // Omite el resto del bucle while             // Resto del bucle while ... } 

El whilebucle en el código anterior lee caracteres llamando a , omitiendo las declaraciones en el cuerpo del bucle si los caracteres son espacios.GetChar()

Manejo de excepciones

El método de manejo de excepciones en tiempo de ejecución en C# se hereda de Java y C++.

La biblioteca de clases base tiene una clase llamada de la que se derivan todas las demás clases de excepción. Un objeto contiene toda la información sobre una excepción específica y también las excepciones internas que se generaron. Los programadores pueden definir sus propias excepciones derivando de la clase.System.ExceptionExceptionException

Se puede lanzar una excepción de esta manera:

lanzar nueva NotImplementedException ();  

try ... catch ... finallydeclaraciones

Las excepciones se gestionan dentro de bloques.try ... catch

try { // Declaraciones que pueden generar excepciones ... } catch ( Exception ex ) { // Excepción capturada y manejada aquí ... } finally { // Declaraciones siempre ejecutadas después de los bloques try/catch ... }        

Las instrucciones dentro del trybloque se ejecutan y, si alguna de ellas genera una excepción, se interrumpe la ejecución del bloque y el catchbloque se encarga de gestionar la excepción. Puede haber varios catchbloques, en cuyo caso se ejecuta el primer bloque con una variable de excepción cuyo tipo coincida con el tipo de la excepción generada.

Si ningún bloque coincide con el tipo de la excepción lanzada, se interrumpe catchla ejecución del bloque (o método) externo que contiene la declaración y la excepción se transmite hacia arriba y fuera del bloque o método que la contiene. La excepción se propaga hacia arriba a través de la pila de llamadas hasta que se encuentra un bloque coincidente dentro de uno de los métodos activos actualmente. Si la excepción se propaga hasta el método superior sin que se encuentre un bloque coincidente, se finaliza todo el programa y se escribe una descripción textual de la excepción en el flujo de salida estándar.try ... catchcatchMain()catch

Las instrucciones dentro del finallybloque siempre se ejecutan después de los bloques tryy catch, independientemente de si se generó una excepción o no. Estos bloques son útiles para proporcionar código de limpieza.

Al catchbloque debe seguir un bloque, un finallybloque o ambos try.

Tipos

C# es un lenguaje de tipado estático, como C y C++. Esto significa que cada variable y constante obtiene un tipo fijo cuando se declara. Existen dos tipos de tipos: tipos de valor y tipos de referencia .

Tipos de valores

Las instancias de los tipos de valor residen en la pila, es decir, están vinculadas a sus variables. Si se declara una variable para un tipo de valor, la memoria se asigna directamente. Si la variable queda fuera del ámbito, el objeto se destruye con ella.

Estructuras

Las estructuras se conocen más comúnmente como structs . Las estructuras son tipos de valores definidos por el usuario que se declaran mediante la structpalabra clave. Son muy similares a las clases, pero son más adecuadas para tipos livianos. Más adelante en este artículo se presentan algunas diferencias sintácticas importantes entre una clase y una estructura.

estructura Foo { ... }  

Los tipos de datos primitivos son todos estructuras.

Tipos predefinidos

Éstos son los tipos de datos primitivos.

Nota: string ( ) no es una estructura y no es un tipo primitivo.System.String

Enumeraciones

Los tipos enumerados (declarados con enum) son valores nombrados que representan valores enteros.

enumeración Temporada { Invierno = 0 , Primavera = 1 , Verano = 2 , Otoño = 3 , Otoño = Otoño // En inglés americano, el otoño se llama Fall. }                 

Las variables de enumeración se inicializan de forma predeterminada en cero. Se les pueden asignar o inicializar los valores con nombre definidos por el tipo de enumeración.

Temporada temporada ; temporada = Temporada . Primavera ;   

Las variables de tipo enumeración son valores enteros. Se permite la suma y resta entre variables del mismo tipo sin ninguna conversión específica, pero la multiplicación y la división son algo más riesgosas y requieren una conversión explícita. Las conversiones también son necesarias para convertir variables de enumeración a y desde tipos enteros. Sin embargo, la conversión no generará una excepción si el valor no está especificado por la definición de tipo.

temporada = ( Temporada ) 2 ; // convierte 2 en un valor de enumeración de tipo Temporada. temporada = temporada + 1 ; // Agrega 1 al valor. temporada = temporada + temporada2 ; // Agrega los valores de dos variables de enumeración. int valor = ( int ) temporada ; // Convierte el valor de enumeración en un valor entero.                 temporada ++ ; // Temporada.Primavera (1) se convierte en Temporada.Verano (2). temporada -- ; // Temporada.Verano (2) se convierte en Temporada.Primavera (1).  

Los valores se pueden combinar utilizando el operador OR bit a bit |.

Color myColors = Color . Verde | Color . Amarillo | Color . Azul ;       

Tipos de referencia

Las variables creadas para los tipos de referencia son referencias administradas con tipos. Cuando se llama al constructor, se crea un objeto en el montón y se asigna una referencia a la variable. Cuando una variable de un objeto queda fuera del ámbito, la referencia se interrumpe y, cuando no quedan referencias, el objeto se marca como basura. El recolector de basura lo recogerá y lo destruirá en breve.

Una variable de referencia es nula cuando no hace referencia a ningún objeto.

Matrices

Un tipo de matriz es un tipo de referencia que hace referencia a un espacio que contiene uno o más elementos de un tipo determinado. Todos los tipos de matriz derivan de una clase base común, . Se hace referencia a cada elemento por su índice, al igual que en C++ y Java.System.Array

Una matriz en C# es lo que se llamaría una matriz dinámica en C++.

int [] números = new int [ 2 ]; números [ 0 ] = 2 ; números [ 1 ] = 5 ; int x = números [ 0 ];           
Inicializadores

Los inicializadores de matrices proporcionan una sintaxis conveniente para la inicialización de matrices.

// Sintaxis larga int [] números = new int [ 5 ]{ 20 , 1 , 42 , 15 , 34 }; // Sintaxis corta int [] números2 = { 20 , 1 , 42 , 15 , 34 }; // Sintaxis inferida var números3 = new [] { 20 , 1 , 42 , 15 , 34 };                             
Matrices multidimensionales

Las matrices pueden tener más de una dimensión, por ejemplo, 2 dimensiones para representar una cuadrícula.

int [,] números = new int [ 3 , 3 ]; números [ 1 , 2 ] = 2 ;       int [,] números2 = nuevo int [ 3 , 3 ] { { 2 , 3 , 2 }, { 1 , 2 , 6 }, { 2 , 4 , 5 } };                

Véase también

Clases

Las clases son tipos de referencia definidos por el usuario que se describen a sí mismos. Básicamente, todos los tipos en .NET Framework son clases, incluidas las estructuras y las enumeraciones, que son clases generadas por el compilador. Los miembros de clase son privatede forma predeterminada, pero se pueden declarar para publicque sean visibles fuera de la clase o protectedpara que sean visibles para cualquier descendiente de la clase.

Instrumentos de cuerda

La clase, o simplemente , representa una secuencia inmutable de caracteres Unicode ( ).System.Stringstringchar

Las acciones realizadas en una cadena siempre devolverán una nueva cadena.

cadena texto = "¡Hola mundo!" ; cadena substr = texto . Substring ( 0,5 ); cadena [] partes = texto . Split ( new char []{ ' ' }) ;             

La clase se puede utilizar cuando se desea una "cadena" mutable.System.StringBuilder

var sb = new StringBuilder ( ); sb.Append ( 'H' ) ; sb.Append ( "el" ) ; sb.AppendLine ( "lo ! " ) ;    

Interfaz

Las interfaces son estructuras de datos que contienen definiciones de miembros sin implementación real. Una variable de un tipo de interfaz es una referencia a una instancia de una clase que implementa esta interfaz. Consulte #Interfaces.

Delegados

C# proporciona punteros de función orientados a objetos con seguridad de tipos en forma de delegados .

clase Programa { // Tipo de delegado: delegado int Operación ( int a , int b );         int estático Agregar ( int i1 , int i2 ) { devolver i1 + i2 ; }            int estático Sub ( int i1 , int i2 ) { devolver i1 - i2 ; }            static void Main () { // Instanciar el delegado y asignarle el método. Operación op = Add ;         // Llamar al método al que apunta el delegado. int result1 = op ( 2 , 3 ); // 5       op = Sub ; int resultado2 = op ( 10 , 2 ); // 8 } }         

Inicializando el delegado con un método anónimo.

adición = delegado ( int a , int b ) { return a + b ; };           

Inicializando el delegado con expresión lambda.

suma = ( a , b ) => a + b ;       

Eventos

Los eventos son punteros que pueden apuntar a múltiples métodos. Más exactamente, vinculan punteros de métodos a un identificador. Por lo tanto, esto puede verse como una extensión de los delegados . Por lo general, se utilizan como activadores en el desarrollo de la interfaz de usuario. La forma utilizada en C# y el resto de la infraestructura del lenguaje común se basa en la del Visual Basic clásico .

delegado void MouseEventHandler ( objeto remitente , MouseEventArgs e );     clase pública Botón : Sistema.Windows.Controles.Control { evento privado MouseEventHandler _onClick ;         /* Función de disparo imaginaria */ void Click () { _onClick ( this , new MouseEventArgs ( data )); } }       

Un evento requiere un controlador de eventos que lo acompañe y que se crea a partir de un delegado especial que, en una biblioteca específica de la plataforma como Windows Presentation Foundation y Windows Forms, generalmente toma dos parámetros: el remitente y los argumentos del evento . El tipo del objeto de argumento del evento se deriva de la clase EventArgs, que es parte de la biblioteca base de la CLI.

Una vez declarado en su clase, la única forma de invocar el evento es desde dentro del propietario. Se puede implementar un método de escucha externo para que se active cuando se dispara el evento.

clase pública MainWindow : System . Windows . Controls . Window { privada Button _button1 ;        public MainWindow () { _button1 = new Button (); _button1.Text = " ¡Haz clic en mí! " ;          /* Suscribirse al evento */ _button1 . ClickEvent += Button1_OnClick ;    /* Sintaxis alternativa que se considera antigua:  _button1.MouseClick += new MouseEventHandler(Button1_OnClick); */ }  void protegido Button1_OnClick ( objeto remitente , MouseEventArgs e ) { MessageBox . Show ( "¡Hizo clic!" ); } }        

También es posible implementar eventos personalizados:

manejador de eventos privado _clickHandles = ( s , e ) => { };        evento público EventHandler Click { add { // Algún código para ejecutar cuando se agrega el controlador... ...   _clickHandles += valor ; } eliminar { // Algún código para ejecutar cuando se elimina el controlador... ...  _clickHandles -= valor ; } }  

Véase también

Tipos que aceptan valores NULL

Esta es una característica de C# 2.0 .

Los tipos que aceptan valores nulos se introdujeron en C# 2.0, en primer lugar, para permitir que los tipos de valor sean nulos (algo útil cuando se trabaja con una base de datos).

int? n = 2 ; n = nulo ;     Consola .WriteLine ( n.HasValue ) ;

En realidad esto es lo mismo que utilizar la estructura.Nullable<T>

Nulo < int > n = 2 ; n = nulo ;     Consola .WriteLine ( n.HasValue ) ;

Punteros

C# tiene y permite punteros a tipos seleccionados (algunas primitivas, enumeraciones, cadenas, punteros e incluso matrices y estructuras si contienen solo tipos a los que se puede apuntar [6] ) en un contexto no seguro: métodos y bloques de código marcados unsafe. Estos son sintácticamente iguales a los punteros en C y C++. Sin embargo, la comprobación en tiempo de ejecución está deshabilitada dentro de unsafelos bloques.

void estático Main ( cadena [] argumentos ) { inseguro { int a = 2 ; int * b = &a a ;              Consola . WriteLine ( "Dirección de a: {0}. Valor: {1}" , ( int ) &a a , a ); Console . WriteLine ( "Dirección de b: {0}. Valor: {1}. Valor de *b: {2}" , ( int ) & b , ( int ) b , * b );       // Generará algo como: // Dirección de a: 71953600. Valor: 2 // Dirección de b: 71953596. Valor: 71953600. Valor de *b: 2 } }   

Solo se requiere que las estructuras sean estructuras puras sin miembros de un tipo de referencia administrado, por ejemplo, una cadena o cualquier otra clase.

pública struct MyStruct { pública char Carácter ; pública int Entero ; }        estructura pública MyContainerStruct { byte público Byte ; MyStruct pública MyStruct ; }        

En uso:

MiEstructuraContenedor x ; MiEstructuraContenedor * ptr = & x ;    valor del byte = ptr -> Byte ;   

Dinámica

Esta es una característica de C# 4.0 y .NET Framework 4.0 .

El tipo dynamices una característica que permite la búsqueda dinámica en tiempo de ejecución en C# de manera estática. Dinámico denota una variable con un objeto con un tipo que se resuelve en tiempo de ejecución, a diferencia del tiempo de compilación, como se hace normalmente.

Esta característica aprovecha el Dynamic Language Runtime (DLR) y ha sido diseñada específicamente con el objetivo de interoperar con lenguajes tipados dinámicamente como IronPython e IronRuby (implementaciones de Python y Ruby para .NET).

El soporte dinámico también facilita la interoperación con objetos COM .

dynamic x = new Foo (); x.DoSomething (); // Se compilará y resolverá en tiempo de ejecución. Se lanzará una excepción si no es válida .     

Tipos anónimos

Esta es una característica de C# 3.0 .

Los tipos anónimos son clases sin nombre que genera el compilador. Solo se pueden utilizar y, sin embargo, son muy útiles en un escenario como cuando se tiene una consulta LINQ que devuelve un objeto selecty solo se quieren devolver algunos valores específicos. Luego, se define un tipo anónimo que contenga campos de solo lectura generados automáticamente para los valores.

Al crear una instancia de otra declaración de tipo anónimo con la misma firma, el compilador infiere automáticamente el tipo.

var carl = new { Name = "Carl" , Age = 35 }; // El nombre del tipo solo lo conoce el compilador. var mary = new { Name = "Mary" , Age = 22 }; // El mismo tipo que la expresión anterior                        

Empaquetado y desempaquetado

El boxeo es la operación de convertir un valor de un tipo de valor en un valor de un tipo de referencia correspondiente. [7] El boxeo en C# es implícito.

El unboxing es la operación de convertir un valor de un tipo de referencia (previamente encajonado) en un valor de un tipo de valor. [7] El unboxing en C# requiere una conversión de tipo explícita.

Ejemplo:

int foo = 42 ; // Tipo de valor. object bar = foo ; // foo se convierte en bar. int foo2 = ( int ) bar ; // Se desempaqueta nuevamente al tipo de valor.            

Programación orientada a objetos (POO)

C# tiene soporte directo para la programación orientada a objetos .

Objetos

Se crea un objeto con el tipo como plantilla y se denomina instancia de ese tipo particular.

En C#, los objetos son referencias o valores. No se hace ninguna distinción sintáctica entre ellos en el código.

Clase de objeto

Todos los tipos, incluso los tipos de valor en su forma encapsulada, heredan implícitamente de la clase, la clase base definitiva de todos los objetos. Esta clase contiene los métodos más comunes que comparten todos los objetos. Algunos de ellos son y pueden ser anulados.System.Objectvirtual

Las clases heredan directa o indirectamente a través de otra clase base.System.Object

Miembros
Algunos de los miembros de la clase Object:

Clases

Las clases son elementos fundamentales de un lenguaje orientado a objetos como C#. Sirven como plantilla para los objetos. Contienen miembros que almacenan y manipulan datos de una manera similar a la de la vida real.

Diferencias entre clases y estructuras

Aunque las clases y las estructuras son similares tanto en la forma en que se declaran como en la forma en que se utilizan, existen algunas diferencias significativas. Las clases son tipos de referencia y las estructuras son tipos de valor. Una estructura se asigna en la pila cuando se declara y la variable se vincula a su dirección. Contiene directamente el valor. Las clases son diferentes porque la memoria se asigna como objetos en el montón. Las variables son más bien punteros administrados en la pila que apuntan a los objetos. Son referencias.

Las estructuras difieren de las clases en varios otros aspectos. Por ejemplo, si bien ambas ofrecen un constructor predeterminado implícito que no acepta argumentos, no se puede redefinir para las estructuras. Definir explícitamente un constructor con parámetros diferentes suprimirá el constructor predeterminado implícito en las clases, pero no en las estructuras. Todos los campos de una estructura deben inicializarse en esos tipos de constructores. Las estructuras no tienen finalizadores y no pueden heredar de otra clase como lo hacen las clases. Implícitamente, están selladas y heredan de (que hereda de ). Las estructuras son más adecuadas para cantidades más pequeñas de datos.System.ValueTypeSystem.Object

Este es un breve resumen de las diferencias:

  1. ^ Se genera solo si no se proporcionó ningún otro constructor
  2. ^ Siempre se genera automáticamente y el programador no puede escribirlo.

Declaración

Una clase se declara así:

clase Foo { // Declaraciones de miembros }  
Clase parcial
Esta es una característica de C# 2.0 .

Una clase parcial es una declaración de clase cuyo código se divide en archivos separados. Las distintas partes de una clase parcial deben estar marcadas con la palabra clave partial.

// Archivo1.cs clase parcial Foo { ... }   // Archivo2.cs clase parcial Foo { ... }   

La razón habitual para usar clases parciales es dividir alguna clase en una parte mantenida por el programador y una parte mantenida por la herramienta, es decir, parte del código se genera automáticamente mediante una herramienta de diseño de interfaz de usuario o algo similar.

Inicialización

Antes de poder utilizar los miembros de la clase, inicialice la variable con una referencia a un objeto. Para crearlo, llame al constructor apropiado utilizando la newpalabra clave. Tiene el mismo nombre que la clase.

var foo = nuevo Foo ();    

En el caso de las estructuras, es opcional llamar explícitamente a un constructor, ya que el predeterminado se llama automáticamente. Solo es necesario declararlo y se inicializa con valores estándar.

Inicializadores de objetos
Esta es una característica de C# 3.0 .

Proporciona una forma más conveniente de inicializar los campos y propiedades públicos de un objeto. Las llamadas al constructor son opcionales cuando hay un constructor predeterminado.

var persona = nueva Persona { Nombre = "John Doe" , Edad = 39 };          // Igual a var persona = new Persona (); persona . Nombre = "John Doe" ; persona . Edad = 39 ;        
Inicializadores de colección
Esta es una característica de C# 3.0 .

Los inicializadores de colecciones brindan una sintaxis similar a una matriz para inicializar colecciones. El compilador simplemente generará llamadas al método Add. Esto funciona para las clases que implementan la interfaz ICollection.

var lista = nueva Lista < int > { 2 , 5 , 6 , 6 };        // Igual a var lista = new Lista < int > (); lista . Agregar ( 2 ); lista . Agregar ( 5 ); lista . Agregar ( 6 ); lista . Agregar ( 6 );    

Acceder a miembros

Se accede a los miembros de una instancia y a los miembros estáticos de una clase mediante el .operador.

Acceso a un miembro de instancia
Se puede acceder a los miembros de instancia a través del nombre de una variable.

cadena foo = "Hola" ; cadena fooUpper = foo . ToUpper ();      

Cómo acceder a un miembro de clase estático
Se accede a los miembros estáticos utilizando el nombre de la clase u otro tipo.

int r = cadena . Comparar ( foo , fooUpper );    

Acceder a un miembro a través de un puntero
En código inseguro , se accede a los miembros de un valor (tipo de estructura) referenciado por un puntero con el ->operador tal como en C y C++.

PUNTO p ; p . X = 2 ; p . Y = 6 ; PUNTO * ptr = & p ; ptr -> Y = 4 ;          

Modificadores

Los modificadores son palabras clave que se utilizan para modificar las declaraciones de tipos y miembros de tipos. En particular, existe un subgrupo que contiene los modificadores de acceso.

Modificadores de clase
Modificadores de miembros de clase
staticmodificador

El staticmodificador indica que un miembro pertenece a la clase y no a un objeto específico. Las clases marcadas como estáticas solo pueden contener miembros estáticos. A veces, los miembros estáticos se denominan miembros de clase, ya que se aplican a la clase en su totalidad y no a sus instancias.

clase pública Foo { public static void Something () { ... } } // Llamar al método de la clase Foo . Something ();         
Modificadores de acceso

Los modificadores de acceso , o modificadores de herencia , establecen la accesibilidad de las clases, métodos y otros miembros. publicSe puede acceder a algo marcado desde cualquier lugar. privateSolo se puede acceder a los miembros desde dentro de la clase en la que se declaran y se ocultarán cuando se hereden. Los miembros con el protectedmodificador serán private, pero accesibles cuando se hereden. internalLas clases y los miembros solo serán accesibles desde el interior del ensamblado declarante.

Las clases y estructuras son implícitas internaly los miembros son implícitos privatesi no tienen un modificador de acceso.

clase pública Foo { int público Do () { devolver 0 ; }          clase pública Bar {    } }

Esta tabla define dónde se pueden utilizar los modificadores de acceso.

Constructores

Un constructor es un método especial que se llama automáticamente cuando se crea un objeto. Su propósito es inicializar los miembros del objeto. Los constructores tienen el mismo nombre que la clase y no devuelven nada explícitamente. Implícitamente, devolverán el objeto recién creado cuando se los llame a través del newoperador. Pueden tomar parámetros como cualquier otro método. El constructor sin parámetros es especial porque se puede especificar como una restricción necesaria para un parámetro de tipo genérico.

clase Foo { Foo () { ... } }     

Los constructores pueden ser public, private, protectedo internal.

Incinerador de basuras

El destructor se llama cuando el recolector de elementos no utilizados está recolectando el objeto para realizar una limpieza manual. Hay un método destructor predeterminado que se llama finalizey que se puede anular declarando uno.

La sintaxis es similar a la de los constructores. La diferencia es que el nombre va precedido de un ~ y no puede contener ningún parámetro. No puede haber más de un destructor.

clase Foo { ...   ~ Foo () { ... } }   

Los finalizadores son siempre private.

Métodos

Al igual que en C y C++, existen funciones que agrupan código reutilizable. La principal diferencia es que las funciones, al igual que en Java, deben residir dentro de una clase. Por lo tanto, una función se denomina método . Un método tiene un valor de retorno, un nombre y, por lo general, algunos parámetros que se inicializan cuando se lo llama con algunos argumentos. Puede pertenecer a una instancia de una clase o ser un miembro estático.

clase Foo { int Bar ( int a , int b ) { devolver a % b ; } }          

Se llama a un método. utilizando la notación de una variable específica o, como en el caso de los métodos estáticos, el nombre de un tipo.

Foo foo = nuevo Foo (); int r = foo . Bar ( 7 , 2 );        Consola .WriteLine ( r ) ;
refy outparámetros

Se pueden hacer que los argumentos se pasen por referencia de forma explícita cuando se llama a un método con parámetros precedidos por palabras clave refo out. Estos punteros administrados resultan útiles cuando se pasan variables que se desea modificar dentro del método por referencia. La principal diferencia entre ambos es que outse debe haber asignado un parámetro dentro del método antes de que el método regrese. refpuede o no asignar un nuevo valor, pero la variable del parámetro debe inicializarse antes de llamar a la función.

vacío PassRef ( ref int x ) { si ( x == 2 ) x = 10 ; } int Z = 7 ; PassRef ( ref Z );              void PassOut ( salida int x ) { x = 2 ; } int Q ; PassOut ( salida Q );        
Parámetros opcionales
Esta es una característica de C# 4.0 .

C# 4.0 introduce parámetros opcionales con valores predeterminados como los que se ven en C++. Por ejemplo:

void Incremento ( ref int x , int dx = 1 ) { x += dx ; }          int x = 0 ; Incremento ( ref x ); // dx toma el valor predeterminado de 1 Incremento ( ref x , 2 ); // dx toma el valor 2        

Además, para complementar los parámetros opcionales, es posible especificar explícitamente los nombres de los parámetros en las llamadas de método, lo que permite pasar de forma selectiva cualquier subconjunto dado de parámetros opcionales para un método. La única restricción es que los parámetros nombrados deben colocarse después de los parámetros sin nombre. Los nombres de los parámetros se pueden especificar tanto para los parámetros opcionales como para los obligatorios, y se pueden utilizar para mejorar la legibilidad o reordenar arbitrariamente los argumentos en una llamada. Por ejemplo:

Stream OpenFile ( cadena nombre , modo FileMode = FileMode.Open , acceso FileAccess = FileAccess.Read ) { ... }            OpenFile ( "file.txt" ); // usa valores predeterminados para "mode" y "access" OpenFile ( "file.txt" , mode : FileMode . Create ); // usa el valor predeterminado para "access" OpenFile ( "file.txt" , access : FileAccess . Read ); // usa el valor predeterminado para "mode" OpenFile ( name : "file.txt" , access : FileAccess . Read , mode : FileMode . Create ); // nombra todos los parámetros para mayor legibilidad, // y usa un orden diferente al de la declaración del método            

Los parámetros opcionales facilitan la interoperabilidad con COM. Anteriormente, C# tenía que pasar todos los parámetros en el método del componente COM, incluso aquellos que eran opcionales. Por ejemplo:

objeto fileName = "Test.docx" ; objeto missing = System . Reflection . Missing . Value ;      doc . SaveAs ( ref fileName , ref missing , ref missing , ref missing , ref missing , ref missing , ref missing , ref missing , ref missing , ref missing , ref missing , ref missing , ref missing ) ; console . writeline ( " Archivo guardado exitosamente " ) ;                               

Con soporte para parámetros opcionales, el código se puede acortar como

doc .SaveAs ( ref nombreArchivo ) ; 
extern

Una característica de C# es la capacidad de llamar a código nativo. La firma de un método se declara simplemente sin cuerpo y se marca como . También es necesario agregar externel atributo para hacer referencia al archivo DLL deseado.DllImport

[DllImport("win32.dll")] static extern double Pow ( double a , double b );      

Campos

Los campos, o variables de instancia , se pueden declarar dentro del cuerpo de la clase para almacenar datos.

clase Foo { doble foo ; }   

Los campos se pueden inicializar directamente cuando se declaran (a menos que se declaren en la estructura).

clase Foo { doble foo = 2.3 ; }     

Modificadores para campos:

Propiedades

Las propiedades incorporan una sintaxis similar a la de los campos y las combinan con el poder de los métodos. Una propiedad puede tener dos métodos de acceso: gety set.

clase pública Persona { cadena privada _nombre ;      cadena Nombre { obtener { devolver _nombre ; } establecer { _nombre = valor ; } } }              // Usando una propiedad var person = new Person (); person . Name = "Robert" ;      

Modificadores de propiedades:

Modificadores para accesores de propiedad:

Los modificadores predeterminados para los descriptores de acceso se heredan de la propiedad. Tenga en cuenta que los modificadores del descriptor de acceso solo pueden ser iguales o más restrictivos que el modificador de la propiedad.

Propiedades automáticas
Esta es una característica de C# 3.0 .

Una característica de C# 3.0 son las propiedades implementadas automáticamente. Defina los descriptores de acceso sin cuerpos y el compilador generará un campo de respaldo y el código necesario para los descriptores de acceso.

público doble Ancho { obtener ; privado establecer ; }       

Indexadores

Los indexadores añaden capacidades de indexación similares a las de las matrices a los objetos. Se implementan de manera similar a las propiedades.

clase interna IntList { int privado [] _items ;      int este [ int índice ] { obtener { devolver _items [ índice ]; } establecer { _items [ índice ] = valor ; } } }               // Usando un indexador var list = new IntList (); list [ 2 ] = 2 ;      

Herencia

En C#, las clases solo pueden heredar de una clase. Una clase puede derivar de cualquier clase que no esté marcada como sealed.

clase A { } clase B : A {   }
virtual

Los métodos marcados virtualproporcionan una implementación, pero los herederos pueden anularlos mediante el uso de la overridepalabra clave.

La implementación se elige por el tipo real del objeto y no por el tipo de la variable.

clase Operación { público virtual int Do () { return 0 ; } }         clase NewOperation : Operación { public override int Do () { return 1 ; } }           
new

Al sobrecargar un método no virtual con otra firma, newse puede utilizar la palabra clave. El método utilizado se elegirá por el tipo de la variable en lugar del tipo real del objeto.

clase Operación { public int Do () { return 0 ; } }        clase NewOperation : Operación { public new double Do () { return 4.0 ; } }           

Esto demuestra el caso:

operación var = nueva NuevaOperación ();    // Llamará a "double Do()" en NewOperation double d = operación . Do ();   Operación operación_ = operación ;   // Llamará a "int Do()" en la operación int i = operación_ . Do ();   
abstract

Las clases abstractas son clases que solo sirven como plantillas y no se puede inicializar un objeto de ese tipo. De lo contrario, es como una clase normal.

También puede haber miembros abstractos. Los miembros abstractos son miembros de clases abstractas que no tienen ninguna implementación. Deben ser reemplazados por cualquier clase no abstracta que herede el miembro.

clase abstracta Mamífero { public abstract void Walk (); }      clase Humano : Mamífero { público anular void Caminar () {         } ... }
sealed

El sealedmodificador se puede combinar con los demás como un modificador opcional para clases para hacerlas no heredables, o para métodos para no permitir su anulación en clases derivadas.

clase interna sellada Foo { //... }    clase pública Bar { public virtual void Acción () { //... } }         clase pública Baz : Bar { pública sellada anulación void Acción () { //... } }            

Interfaces

Las interfaces son estructuras de datos que contienen definiciones de miembros y no una implementación real. Son útiles cuando se desea definir un contrato entre miembros de diferentes tipos que tienen diferentes implementaciones. Se pueden declarar definiciones de métodos, propiedades e indexadores. Los miembros de la interfaz son implícitamente públicos. Una interfaz puede implementarse de forma implícita o explícita.

interfaz IBinaryOperation { doble A { obtener ; establecer ; } doble B { obtener ; establecer ; }              doble GetResult (); } 

Implementando una interfaz

Una interfaz es implementada por una clase o extendida por otra interfaz de la misma manera que una clase se deriva de otra clase utilizando la :notación.

Implementación implícita

Al implementar implícitamente una interfaz, los miembros de la interfaz deben ser public.

clase pública Adder : IBinaryOperation { pública doble A { obtener ; establecer ; } pública doble B { obtener ; establecer ; }                   público doble GetResult () { devolver A + B ; } }        clase pública Multiplicador : IBinaryOperation { pública doble A { obtener ; establecer ; } pública doble B { obtener ; establecer ; }                   público doble GetResult () { devolver A * B ; } }        

En uso:

IBinaryOperation op = null ; doble resultado ;    // Adder implementa la interfaz IBinaryOperation.op = nuevo sumador (); op . Un = 2 ; op . B = 3 ;       resultado = op . ObtenerResultado (); // 5   // Multiplicador también implementa la interfaz.op = nuevo multiplicador (); op . Una = 5 ; op . B = 4 ;       resultado = op . ObtenerResultado (); // 20   

Implementación explícita

También se pueden implementar miembros de forma explícita. Los miembros de la interfaz que se implementan explícitamente mediante una clase son accesibles solo cuando el objeto se maneja como el tipo de interfaz.

clase pública Adder : IBinaryOperation { double IBinaryOperation.A { obtener ; establecer ; } double IBinaryOperation.B { obtener ; establecer ; }                 doble IBinaryOperation . GetResult () { return (( IBinaryOperation ) this ). A + (( IBinaryOperation ) this ). B ; } }       

En uso:

Sumador add = new Sumador ();    // Estos miembros no son accesibles: // add.A = 2; // add.B = 3; // double result = add.GetResult();// Convertir al tipo de interfaz para acceder a ellos: IBinaryOperation add2 = add ; add2 . A = 2 ; add2 . B = 3 ;       doble resultado = add2 .GetResult ( );   

Nota: Las propiedades de la clase que se extiende IBinaryOperationson implementadas automáticamente por el compilador y se agrega automáticamente un campo de respaldo (ver #Propiedades automáticas).

Ampliación de múltiples interfaces

Se permite que las interfaces y clases extiendan múltiples interfaces.

clase MiClase : IInterfaceA , IInterfaceB { ... }     

Aquí hay una interfaz que extiende dos interfaces.

interfaz IInterfaceC : IInterfaceA , IInterfaceB { ... }     

Interfaces vs. clases abstractas

Las interfaces y las clases abstractas son similares. A continuación se describen algunas diferencias importantes:

Genéricos

Esta es una característica de C# 2.0 y .NET Framework 2.0 .

Los tipos genéricos (o tipos parametrizados, polimorfismo paramétrico ) utilizan parámetros de tipo, lo que permite diseñar clases y métodos que no especifican el tipo utilizado hasta que se crea una instancia de la clase o el método. La principal ventaja es que se pueden utilizar parámetros de tipo genéricos para crear clases y métodos que se pueden utilizar sin incurrir en el coste de las operaciones de conversión en tiempo de ejecución o de boxing, como se muestra aquí: [8]

//Declarar la clase genérica.clase pública GenericList < T > { void Add ( T entrada ) { } }       clase TestGenericList { clase privada ExampleClass { } static void Main () { // Declarar una lista de tipo int. var list1 = new GenericList < int > ();                 // Declarar una lista de tipo cadena. var list2 = new GenericList < string > ();      // Declarar una lista de tipo ExampleClass. var list3 = new GenericList < ExampleClass > (); } }      

En comparación con las plantillas de C++ , los genéricos de C# pueden proporcionar una mayor seguridad, pero también tienen capacidades algo limitadas. [9] Por ejemplo, no es posible llamar a operadores aritméticos en un tipo genérico de C#. [10] A diferencia de las plantillas de C++, los tipos parametrizados de .NET se instancian en tiempo de ejecución en lugar de por el compilador; por lo tanto, pueden ser multilingües, mientras que las plantillas de C++ no. Admiten algunas características que no son compatibles directamente con las plantillas de C++, como las restricciones de tipo en parámetros genéricos mediante el uso de interfaces. Por otro lado, C# no admite parámetros genéricos que no sean de tipo.

A diferencia de los genéricos en Java, los genéricos .NET utilizan la reificación para convertir los tipos parametrizados en objetos de primera clase en la máquina virtual de infraestructura de lenguaje común (CLI), lo que permite optimizaciones y la preservación de la información de tipo. [11]

Uso de genéricos

Clases genéricas

Las clases y estructuras pueden ser genéricas.

clase pública Lista < T > { ... public void Add ( T item ) { ... } }          var lista = nueva Lista < int > (); lista . Agregar ( 6 ); lista . Agregar ( 2 );    

Interfaces genéricas

interfaz IEnumerable < T > { ... }  

Delegados genéricos

delegado R Func < T1 , T2 , R > ( T1 a1 , T2 a2 );       

Métodos genéricos

público estático T [] CombineArrays < T > ( T [] a , T [] b ) { T [] newArray = new T [ a . Length + b . Length ]; a . CopyTo ( newArray , 0 ); b . CopyTo ( newArray , a . Length ); return newArray ; }                   cadena [] a = nueva cadena [] { "a" , "b" , "c" }; cadena [] b = nueva cadena [] { "1" , "2" , "3" }; cadena [] c = CombineArrays ( a , b );                      doble [] da = nuevo doble [] { 1.2 , 2.17 , 3.141592 }; doble [] db = nuevo doble [] { 4.44 , 5.6 , 6.02 }; doble [] dc = CombineArrays ( da , db );                      // c es una matriz de cadenas que contiene { "a", "b", "c", "1", "2", "3"} // dc es una matriz doble que contiene { 1.2, 2.17, 3.141592, 4.44, 5.6, 6.02}

Parámetros de tipo

Los parámetros de tipo son nombres que se utilizan en lugar de tipos concretos cuando se define un nuevo genérico. Pueden asociarse con clases o métodos colocando el parámetro de tipo entre corchetes angulares . Al instanciar (o llamar) un genérico, se puede sustituir un tipo concreto por el parámetro de tipo que se proporcionó en su declaración. Los parámetros de tipo se pueden restringir mediante el uso de la palabra clave y una especificación de restricción; se puede utilizar cualquiera de las seis restricciones separadas por comas: [12]< >where

Covarianza y contravarianza

Esta es una característica de C# 4.0 y .NET Framework 4.0 .

Las interfaces genéricas y los delegados pueden tener sus parámetros de tipo marcados como covariantes o contravariantes , utilizando las palabras claves outy in, respectivamente. Estas declaraciones se respetan para las conversiones de tipo, tanto implícitas como explícitas, y tanto en tiempo de compilación como en tiempo de ejecución. Por ejemplo, la interfaz existente se ha redefinido de la siguiente manera:IEnumerable<T>

interfaz IEnumerable < out T > { IEnumerator < T > GetEnumerator (); }    

Por lo tanto, cualquier clase que implemente para alguna clase también se considera compatible con todas las clases e interfaces que la extiendan, directa o indirectamente. En la práctica, permite escribir código como:IEnumerable<Derived>DerivedIEnumerable<Base>BaseDerived

void PrintAll ( IEnumerable < object > objetos ) { foreach ( object o in objetos ) { System.Console.WriteLine ( o ) ; } }          IEnumerable < string > strings = new List < string > (); PrintAll ( strings ); // IEnumerable<string> se convierte implícitamente en IEnumerable<object>     

Para la contravarianza, la interfaz existente se ha redefinido de la siguiente manera:IComparer<T>

interfaz pública IComparer < in T > { int Compare ( T x , T y ); }        

Por lo tanto, cualquier clase que implemente para alguna clase también se considera compatible con para todas las clases e interfaces que se extienden desde . Esto permite escribir código como:IComparer<Base>BaseIComparer<Derived>DerivedBase

IComparer < objeto > objectComparer = GetComparer (); IComparer < cadena > stringComparer = objectComparer ;      

Enumeradores

Un enumerador es un iterador. Los enumeradores se obtienen normalmente llamando al método de un objeto que implementa la interfaz. Las clases contenedoras normalmente implementan esta interfaz. Sin embargo, la sentencia foreach en C# puede operar en cualquier objeto que proporcione dicho método, incluso si no implementa . Esta interfaz se amplió a una versión genérica en .NET 2.0 .GetEnumerator()IEnumerableIEnumerable

A continuación se muestra un uso simple de iteradores en C# 2.0:

// versión explícita IEnumerator < MyType > iter = list . GetEnumerator (); mientras ( iter . MoveNext ()) Console . WriteLine ( iter . Current );     // versión implícita foreach ( MyType valor en lista ) Console . WriteLine ( valor );     

Funcionalidad del generador

Esta es una característica de C# 2.0 .

El marco .NET 2.0 permitió a C# introducir un iterador que proporciona funcionalidad de generador , utilizando una construcción similar a la de Python . [13] Con un , la función mantiene automáticamente su estado durante la iteración.yield returnyieldyield return

// Método que toma una entrada iterable (posiblemente una matriz) // y devuelve todos los números pares. public static IEnumerable < int > GetEven ( IEnumerable < int > numbers ) { foreach ( int i in numbers ) { if ( i % 2 == 0 ) yield return i ; } }                  // usando el método para generar solo números pares de la matriz static void Main () { int [] numbers = { 1 , 2 , 3 , 4 , 5 , 6 }; foreach ( int i in GetEven ( numbers )) Console . WriteLine ( i ); // genera 2, 4 y 6 }                   

LINQ

This is a feature of C# 3.0 and .NET Framework 3.0.

LINQ, short for Language Integrated Queries, is a .NET Framework feature which simplifies the handling of data. Mainly it adds support that allows the querying of arrays, collections, and databases. It also introduces binders, which makes it easier to access to databases and their data.

Query syntax

The LINQ query syntax was introduced in C# 3.0 and lets one write SQL-like queries in C#.

var list = new List<int>{ 2, 7, 1, 3, 9 };var result = from i in list where i > 1 select i;

The statements are compiled into method calls, whereby almost only the names of the methods are specified. Which methods are ultimately used is determined by normal overload resolution. Thus, the end result of the translation is affected by what symbols are in scope.

What differs from SQL is that the from-statement comes first and not last as in SQL. This is because it seems more natural writing like this in C# [citation needed] and supports "Intellisense" (Code completion in the editor).

Anonymous methods

Anonymous methods, or in their present form more commonly referred to as "lambda expressions", is a feature which allows programmers to write inline closure-like functions in their code.

There are various ways to create anonymous methods. Prior to C# 3.0 there was limited support by using delegates.

Anonymous delegates

This is a feature of C# 2.0.

Anonymous delegates are functions pointers that hold anonymous methods. The purpose is to make it simpler to use delegates by simplifying the process of assigning the function. Instead of declaring a separate method in code the programmer can use the syntax to write the code inline and the compiler will then generate an anonymous function for it.

Func<int, int> f = delegate(int x) { return x * 2; };

Lambda expressions

This is a feature of C# 3.0.

Lambda expressions provide a simple syntax for inline functions that are similar to closures. Functions with parameters infer the type of the parameters if other is not explicitly specified.

// [arguments] => [method-body]// With parametersn => n == 2(a, b) => a + b(a, b) => { a++; return a + b; }// With explicitly typed parameters(int a, int b) => a + b// No parameters() => return 0// Assigning lambda to delegateFunc<int, int, int> f = (a, b) => a + b;

Multi-statement lambdas have bodies enclosed by braces and inside of them code can be written like in standard methods.

(a, b) => { a++; return a + b; }

Lambda expressions can be passed as arguments directly in method calls similar to anonymous delegates but with a more aesthetic syntax.

var list = stringList.Where(n => n.Length > 2);

Lambda expressions are essentially compiler-generated methods that are passed via delegates. These methods are reserved for the compiler only and can not be used in any other context.

Extension methods

This is a feature of C# 3.0.

Extension methods are a form of syntactic sugar providing the illusion of adding new methods to the existing class outside its definition. In practice, an extension method is a static method that is callable as if it were an instance method; the receiver of the call is bound to the first parameter of the method, decorated with keyword this:

public static class StringExtensions{ public static string Left(this string s, int n) { return s.Substring(0, n); }}string s = "foo";s.Left(3); // same as StringExtensions.Left(s, 3);

Local functions

This is a feature of C# 7.0.

Local functions can be defined in the body of another method, constructor or property's getter and setter. Such functions have access to all variables in the enclosing scope, including parent method local variables. They are in scope for the entire method, regardless of whether they're invoked before or after their declaration. Access modifiers (public, private, protected) cannot be used with local functions. Also they do not support function overloading. It means there cannot be two local functions in the same method with the same name even if the signatures don't overlap.[14] After a compilation, a local function is transformed into a private static method, but when defined it cannot be marked static.[15]

In code example below, the Sum method is a local function inside Main method. So it can be used only inside its parent method Main:

static void Main(string[] args){ int Sum(int x, int y) { return x + y; } Console.WriteLine(Sum(10, 20)); Console.ReadKey();}

Miscellaneous

Closure blocks

C# implements closure blocks by means of the using statement. The using statement accepts an expression which results in an object implementing IDisposable, and the compiler generates code that guarantees the object's disposal when the scope of the using-statement is exited. The using statement is syntactic sugar. It makes the code more readable than the equivalent try ... finally block.

public void Foo(){ using (var bar = File.Open("Foo.txt")) { // do some work throw new Exception(); // bar will still get properly disposed. }}

Thread synchronization

C# provides the lock statement, which is yet another example of beneficial syntactic sugar. It works by marking a block of code as a critical section by mutual exclusion of access to a provided object. Like the using statement, it works by the compiler generating a try ... finally block in its place.

private static StreamWriter _writer;public void ConcurrentMethod(){ lock (_writer) { _writer.WriteLine("Line 1."); _writer.WriteLine("Followed by line 2."); }}

Attributes

Attributes are entities of data that are stored as metadata in the compiled assembly. An attribute can be added to types and members like properties and methods. Attributes can be used for better maintenance of preprocessor directives.

[CompilerGenerated]public class $AnonymousType$120{ [CompilerGenerated] public string Name { get; set; }}

The .NET Framework comes with predefined attributes that can be used. Some of them serve an important role at runtime while some are just for syntactic decoration in code like CompilerGenerated. It does only mark that it is a compiler-generated element. Programmer-defined attributes can also be created.

An attribute is essentially a class which inherits from the System.Attribute class. By convention, attribute classes end with "Attribute" in their name. This will not be required when using it.

public class EdibleAttribute : Attribute{ public EdibleAttribute() : base() { } public EdibleAttribute(bool isNotPoisonous) { this.IsPoisonous = !isNotPoisonous; } public bool IsPoisonous { get; set; }}

Showing the attribute in use using the optional constructor parameters.

[Edible(true)]public class Peach : Fruit{ // Members if any}

Preprocessor

C# features "preprocessor directives"[16] (though it does not have an actual preprocessor) based on the C preprocessor that allow programmers to define symbols, but not macros. Conditionals such as #if, #endif, and #else are also provided.

Directives such as #region give hints to editors for code folding. The #region block must be terminated with a #endregion directive.

public class Foo{ #region Constructors public Foo() {} public Foo(int firstParam) {} #endregion #region Methods public void IntBar(int firstParam) {} public void StrBar(string firstParam) {} public void BoolBar(bool firstParam) {} #endregion}

Code comments

C# utilizes a double slash (//) to indicate the rest of the line is a comment.

public class Foo{ // a comment public static void Bar(int firstParam) {} // Also a comment}

Multi-line comments can be indicated by a starting slash/asterisk (/*) and ending asterisk/forward slash (*/).

public class Foo{ /* A multi-line comment */ public static void Bar(int firstParam) {}}

Comments do not nest. These are two single comments:

// Can put /* */ */ */ /* /*
/* Can put /* /* /* but it ends with */

Single-line comments beginning with three slashes are used for XML documentation. This, however, is a convention used by Visual Studio and is not part of the language definition:

 /// <summary> /// This class is very classy. /// </summary>

XML documentation comments

C#'s documentation comments[17] are similar to Java's Javadoc, but based on XML. Two methods of documentation are currently supported by the C# compiler.

Single-line documentation comments, such as those commonly found in Visual Studio generated code, are indicated on a line beginning with ///.

public class Foo{ /// <summary>A summary of the method.</summary> /// <param name="firstParam">A description of the parameter.</param> /// <remarks>Remarks about the method.</remarks> public static void Bar(int firstParam) {}}

Multi-line documentation comments, while defined in the version 1.0 language specification, were not supported until the .NET 1.1 release.[18] These comments are designated by a starting forward slash/asterisk/asterisk (/**) and ending asterisk/forward slash (*/).[19]

public class Foo{ /** <summary>A summary of the method.</summary> * <param name="firstParam">A description of the parameter.</param> * <remarks>Remarks about the method.</remarks> */ public static void Bar(int firstParam) {}}

There are some stringent criteria regarding white space and XML documentation when using the forward slash/asterisk/asterisk (/**) technique.

This code block:

/** * <summary> * A summary of the method.</summary>*/

produces a different XML comment than this code block:[19]

/** * <summary> A summary of the method.</summary>*/

Syntax for documentation comments and their XML markup is defined in a non-normative annex of the ECMA C# standard. The same standard also defines rules for processing of such comments, and their transformation to a plain XML document with precise rules for mapping of Common Language Infrastructure (CLI) identifiers to their related documentation elements. This allows any C# integrated development environment (IDE) or other development tool to find documentation for any symbol in the code in a certain well-defined way.

Async-await syntax

This is a feature of C# 5.0 and .NET Framework 4.0.

As of .NET Framework 4 there is a task library that makes it easier to write parallel and multi-threaded applications through tasks.

C# 5.0 has native language support for asynchrony.

Consider this code that takes advantage of the task library directly:

public static class SomeAsyncCode{ public static Task<XDocument> GetContentAsync() { var httpClient = new HttpClient(); return httpClient.GetStringAsync("https://www.contoso.com/").ContinueWith((task) => { string responseBodyAsText = task.Result; return XDocument.Parse(responseBodyAsText); }); }}var t = SomeAsyncCode.GetContentAsync().ContinueWith((task) => { var xmlDocument = task.Result;});t.Start();

Here is the same logic written in the async-await syntax:

public static class SomeAsyncCode{ public static async Task<XDocument> GetContentAsync() { var httpClient = new HttpClient(); string responseBodyAsText = await httpClient.GetStringAsync("https://www.contoso.com/"); return XDocument.Parse(responseBodyAsText); }}var xmlDocument = await SomeAsyncCode.GetContentAsync();// The Task will be started on call with await.

Dialects

Spec#

Spec# is a dialect of C# that is developed in parallel with the standard implementation from Microsoft. It extends C# with specification language features and is a possible future feature to the C# language. It also adds syntax for the code contracts API that was introduced in .NET Framework 4.0. Spec# is being developed by Microsoft Research.

This sample shows two of the basic structures that are used when adding contracts to code.

static void Main(string![] args) requires args.Length > 0{ foreach (string arg in args) { }}

Non-nullable types

Spec# extends C# with non-nullable types that simply checks so the variables of nullable types that has been set as non-nullable are not null. If is null then an exception will be thrown.

 string! input

In use:

public Test(string! input){ ...}

Preconditions

Preconditions are checked before a method is executed.

public Test(int i) requires i > 0;{ this.i = i;}

Postconditions

Postconditions are conditions that are ensured to be correct when a method has been executed.

public void Increment() ensures i > 0;{ i++;}

Checked exceptions

Spec# adds checked exceptions like those in Java.

public void DoSomething() throws SomeException; // SomeException : ICheckedException{ ...}

Checked exceptions are problematic, because when a lower-level function adds a new exception type, the whole chain of methods using this method at some nested lower level must also change its contract. This violates the open/closed principle.[20]

See also

References

  1. ^ "C# Coding Conventions". Microsoft Learn. sec. Naming conventions. Archived from the original on January 16, 2023.
  2. ^ a b Wagner, Bill. "C# Keywords". docs.microsoft.com. Retrieved August 26, 2022.
  3. ^ Schildt, Herbert (December 30, 2008), C# 3.0: The Complete Reference, ISBN 9780071588416
  4. ^ Deitel, Harvey M.; Deitel, Paul J. (November 21, 2005), C# for programmers, ISBN 9780132465915
  5. ^ Wagner, Bill. "Palabras clave de C#". docs.microsoft.com . Consultado el 26 de agosto de 2022 .
  6. ^ Tipos de punteros (Guía de programación de C#)
  7. ^ ab Archer, Parte 2, Capítulo 4: El sistema de tipos
  8. ^ "Generics (C# Programming Guide)" (Genéricos (Guía de programación de C#)). Microsoft . Consultado el 7 de agosto de 2011 .
  9. ^ "Introducción a los genéricos de C#". Microsoft.
  10. ^ "Diferencias entre plantillas de C++ y genéricos de C#". Microsoft.
  11. ^ "Introducción a los genéricos de C#". Microsoft . Enero de 2005 . Consultado el 18 de junio de 2009 .
  12. ^ Restricciones en los parámetros de tipo (Guía de programación de C#) en Microsoft MSDN
  13. ^ "yield". Referencia del lenguaje C# . Microsoft . Consultado el 26 de abril de 2009 .
  14. ^ ".NET Framework - Novedades de C# 7.0". msdn.microsoft.com . Consultado el 8 de abril de 2017 .
  15. ^ "Reflexiones sobre las funciones locales de C# 7". Anton Sizikov . 15 de abril de 2016 . Consultado el 8 de abril de 2017 .
  16. ^ "Directivas del preprocesador de C#". Referencia del lenguaje C# . Microsoft . Consultado el 18 de junio de 2009 .
  17. ^ "Comentarios de la documentación". Microsoft Learn . 15 de junio de 2023.
  18. ^ Horton, Anson (11 de septiembre de 2006). "Preguntas frecuentes sobre comentarios en la documentación XML de C#" . Consultado el 11 de diciembre de 2007 .
  19. ^ ab "Delimitadores para etiquetas de documentación". Referencia del programador de C# . Microsoft . Archivado desde el original el 20 de diciembre de 2008 . Consultado el 18 de junio de 2009 .
  20. ^ Martin, Robert C. (11 de agosto de 2008). "7 Manejo de errores, uso de excepciones no controladas". Clean Code: A Handbook of Agile Software Craftsmanship . Prentice Hall International. ISBN 978-0132350884.
  1. Archer, Tom (2001). C# por dentro . Microsoft Press. ISBN 0-7356-1288-9.
  2. Bart de Smet en Spec# Archivado el 29 de octubre de 2010 en Wayback Machine.

Enlaces externos