Buscar..


Introducción

Las palabras clave son identificadores predefinidos y reservados con un significado especial para el compilador. No se pueden usar como identificadores en su programa sin el prefijo @ . Por ejemplo, @if es un identificador legal pero no la palabra clave if .

Observaciones

C # tiene una colección predefinida de "palabras clave" (o palabras reservadas), cada una de las cuales tiene una función especial. Estas palabras no se pueden usar como identificadores (nombres para variables, métodos, clases, etc.) a menos que se prefijen con @ .

Aparte de estos, C # también usa algunas palabras clave para proporcionar un significado específico en el código. Se llaman palabras clave contextuales. Las palabras clave contextuales se pueden usar como identificadores y no es necesario que tengan un prefijo con @ cuando se usan como identificadores.

stackalloc

La palabra clave stackalloc crea una región de memoria en la pila y devuelve un puntero al inicio de esa memoria. La memoria asignada a la pila se elimina automáticamente cuando se sale del ámbito en el que se creó.

//Allocate 1024 bytes. This returns a pointer to the first byte.
byte* ptr = stackalloc byte[1024];

//Assign some values...
ptr[0] = 109;
ptr[1] = 13;
ptr[2] = 232;
...

Utilizado en un contexto inseguro.

Al igual que con todos los punteros en C #, no hay límites de verificación en las lecturas y asignaciones. La lectura más allá de los límites de la memoria asignada tendrá resultados impredecibles: puede acceder a una ubicación arbitraria dentro de la memoria o puede causar una excepción de infracción de acceso.

//Allocate 1 byte
byte* ptr = stackalloc byte[1];

//Unpredictable results...
ptr[10] = 1;
ptr[-1] = 2;

La memoria asignada a la pila se elimina automáticamente cuando se sale del ámbito en el que se creó. Esto significa que nunca debe devolver la memoria creada con stackalloc o almacenarla más allá de la vida útil del alcance.

unsafe IntPtr Leak() {
    //Allocate some memory on the stack
    var ptr = stackalloc byte[1024];

    //Return a pointer to that memory (this exits the scope of "Leak")
    return new IntPtr(ptr);
}

unsafe void Bad() {
     //ptr is now an invalid pointer, using it in any way will have
     //unpredictable results. This is exactly the same as accessing beyond
     //the bounds of the pointer.
     var ptr = Leak();
}

stackalloc solo se puede utilizar al declarar e inicializar variables. Lo siguiente no es válido:

byte* ptr;
...
ptr = stackalloc byte[1024];

Observaciones:

stackalloc solo debe usarse para optimizaciones de rendimiento (ya sea para computación o interoperabilidad). Esto se debe al hecho de que:

  • El recolector de basura no es necesario ya que la memoria se asigna en la pila en lugar del montón, la memoria se libera tan pronto como la variable queda fuera del alcance
  • Es más rápido asignar memoria en la pila que en el montón
  • Aumente la posibilidad de que la memoria caché llegue a la CPU debido a la ubicación de los datos.

volátil

Agregar la palabra clave volatile a un campo indica al compilador que el valor del campo puede ser cambiado por varios subprocesos separados. El propósito principal de la palabra clave volatile es evitar las optimizaciones del compilador que asumen solo el acceso de un solo hilo. El uso de volatile garantiza que el valor del campo sea el valor más reciente disponible, y que el valor no esté sujeto al almacenamiento en caché que tienen los valores no volátiles.

Es una buena práctica marcar cada variable que puede ser utilizada por múltiples subprocesos como volatile para evitar comportamientos inesperados debido a optimizaciones detrás de escena. Considere el siguiente bloque de código:

public class Example
{
    public int x;

    public void DoStuff()
    {
        x = 5;

        // the compiler will optimize this to y = 15
        var y = x + 10;

        /* the value of x will always be the current value, but y will always be "15" */
        Debug.WriteLine("x = " + x + ", y = " + y);
    }    
}

En el bloque de código anterior, el compilador lee las declaraciones x = 5 y y = x + 10 y determina que el valor de y siempre terminará como 15. Por lo tanto, optimizará la última instrucción como y = 15 . Sin embargo, la variable x es de hecho un campo public y el valor de x se puede modificar en tiempo de ejecución a través de un hilo diferente que actúa en este campo por separado. Ahora considere este código de bloque modificado. Tenga en cuenta que el campo x ahora se declara como volatile .

public class Example
{
    public volatile int x;

    public void DoStuff()
    {
        x = 5;

        // the compiler no longer optimizes this statement
        var y = x + 10;

        /* the value of x and y will always be the correct values */
        Debug.WriteLine("x = " + x + ", y = " + y);
    }    
}

Ahora, el compilador busca los usos de lectura del campo x y asegura que el valor actual del campo siempre se recupera. Esto asegura que incluso si varios subprocesos están leyendo y escribiendo en este campo, el valor actual de x siempre se recupera.

volatile solo puede usarse en campos dentro de las class o struct . Lo siguiente no es válido :

public void MyMethod()
{
    volatile int x;
}

volatile solo puede aplicarse a campos de los siguientes tipos:

  • tipos de referencia o parámetros de tipo genérico conocidos como tipos de referencia
  • tipos primitivos como sbyte , byte , short , ushort , int , uint , char , float y bool
  • enums tipos basados ​​en byte , sbyte , short , ushort , int o uint
  • IntPtr y UIntPtr

Observaciones:

  • El modificador volatile se usa generalmente para un campo al que se accede mediante varios subprocesos sin usar la instrucción de bloqueo para serializar el acceso.
  • La palabra clave volatile se puede aplicar a campos de tipos de referencia
  • La palabra clave volatile no funcionará con primitivos de 64 bits en una plataforma atómica de 32 bits. Las operaciones interbloqueadas, como Interlocked.Read y Interlocked.Exchange , todavía deben usarse para el acceso seguro de múltiples subprocesos en estas plataformas.

fijo

La declaración fija corrige la memoria en una ubicación. Los objetos en la memoria generalmente se mueven alrededor, esto hace posible la recolección de basura. Pero cuando usamos punteros no seguros para las direcciones de memoria, esa memoria no debe ser movida.

  • Usamos la declaración fija para garantizar que el recolector de basura no reubique los datos de cadena.

Variables fijas

var myStr = "Hello world!";

fixed (char* ptr = myStr)
{
    // myStr is now fixed (won't be [re]moved by the Garbage Collector).
    // We can now do something with ptr.
}

Utilizado en un contexto inseguro.

Tamaño del arreglo fijo

unsafe struct Example
{
    public fixed byte SomeField[8];
    public fixed char AnotherField[64];
}

fixed solo se puede usar en campos de una struct (también se debe usar en un contexto inseguro).

defecto

Para las clases, interfaces, delegado, matriz, nullable (como int?) Y tipos de puntero, el default(TheType) devuelve null :

class MyClass {}
Debug.Assert(default(MyClass) == null);
Debug.Assert(default(string) == null);

Para estructuras y enumeraciones, el default(TheType) devuelve lo mismo que el new TheType() :

struct Coordinates
{
    public int X { get; set; }
    public int Y { get; set; }
}

struct MyStruct
{
    public string Name { get; set; }
    public Coordinates Location { get; set; }
    public Coordinates? SecondLocation { get; set; }
    public TimeSpan Duration { get; set; }
}

var defaultStruct = default(MyStruct);
Debug.Assert(defaultStruct.Equals(new MyStruct()));
Debug.Assert(defaultStruct.Location.Equals(new Coordinates()));
Debug.Assert(defaultStruct.Location.X == 0);
Debug.Assert(defaultStruct.Location.Y == 0);
Debug.Assert(defaultStruct.SecondLocation == null);
Debug.Assert(defaultStruct.Name == null);
Debug.Assert(defaultStruct.Duration == TimeSpan.Zero);

default(T) puede ser particularmente útil cuando T es un parámetro genérico para el que no hay restricciones para decidir si T es un tipo de referencia o un tipo de valor, por ejemplo:

public T GetResourceOrDefault<T>(string resourceName)
{
   if (ResourceExists(resourceName))
   {
      return (T)GetResource(resourceName);
   }
   else
   {
      return default(T);
   }
}

solo lectura

La palabra clave readonly es un modificador de campo. Cuando una declaración de campo incluye un modificador de readonly , las asignaciones a ese campo solo pueden ocurrir como parte de la declaración o en un constructor en la misma clase.

La palabra clave readonly es diferente de la palabra clave const . Un campo const solo se puede inicializar en la declaración del campo. Un campo de readonly puede inicializarse en la declaración o en un constructor. Por lo tanto, los campos de readonly pueden tener diferentes valores dependiendo del constructor utilizado.

La palabra clave readonly se usa a menudo cuando se inyectan dependencias.

class Person
{
    readonly string _name;
    readonly string _surname = "Surname";

    Person(string name)
    {
        _name = name;
    }
    void ChangeName()
    {
        _name = "another name"; // Compile error
        _surname = "another surname"; // Compile error
    }
}

Nota: la declaración de un campo de solo lectura no implica inmutabilidad . Si el campo es un tipo de referencia, entonces se puede cambiar el contenido del objeto. Readonly se usa normalmente para evitar que el objeto se sobrescriba y se asigne solo durante la creación de instancias de ese objeto.

Nota: Dentro del constructor se puede reasignar un campo de solo lectura.

public class Car
{
    public double Speed {get; set;}
}

//In code

private readonly Car car = new Car();

private void SomeMethod()
{
    car.Speed = 100;
}

como

La palabra clave as es un operador similar a un reparto . Si una conversión no es posible, usar as produce null lugar de dar como resultado una excepción InvalidCastException .

expression as type es equivalente a expression is type ? (type)expression : (type)null con la advertencia de que, as solo es válido en las conversiones de referencia, las conversiones que admiten expression is type ? (type)expression : (type)null y las conversiones de boxeo. Las conversiones definidas por el usuario no son compatibles; en su lugar se debe usar un elenco regular.

Para la expansión anterior, el compilador genera código de tal manera que la expression solo se evaluará una vez y utilizará la comprobación de tipo dinámico único (a diferencia de los dos en el ejemplo anterior).

as puede ser útil cuando se espera que un argumento facilite varios tipos. Específicamente, se concede al usuario múltiples opciones - en lugar de comprobar todas las posibilidades con is antes de la colada, o simplemente la fundición y la captura de excepciones. Es una buena práctica usar 'como' al lanzar / verificar un objeto, lo que causará solo una penalización de desempaquetado. El uso is para verificar, luego el lanzamiento causará dos penalizaciones de desempaquetado.

Si se espera que un argumento sea una instancia de un tipo específico, se prefiere una conversión regular, ya que su propósito es más claro para el lector.

Debido a que una llamada a as puede producir null , siempre verifique el resultado para evitar una NullReferenceException .

Ejemplo de uso

object something = "Hello";
Console.WriteLine(something as string);        //Hello
Console.Writeline(something as Nullable<int>); //null
Console.WriteLine(something as int?);          //null

//This does NOT compile:
//destination type must be a reference type (or a nullable value type)
Console.WriteLine(something as int);

Demo en vivo en .NET Fiddle

Ejemplo equivalente sin usar as :

Console.WriteLine(something is string ? (string)something : (string)null);

Esto es útil cuando se reemplaza la función de Equals en clases personalizadas.

class MyCustomClass
{

    public override bool Equals(object obj)
    {
        MyCustomClass customObject = obj as MyCustomClass;

        // if it is null it may be really null
        // or it may be of a different type
        if (Object.ReferenceEquals(null, customObject))
        {
            // If it is null then it is not equal to this instance.
            return false;
        }

        // Other equality controls specific to class
    }

}

es

Comprueba si un objeto es compatible con un tipo dado, es decir, si un objeto es una instancia del tipo BaseInterface , o un tipo que se deriva de BaseInterface :

interface BaseInterface {}
class BaseClass : BaseInterface {}
class DerivedClass : BaseClass {}

var d = new DerivedClass();
Console.WriteLine(d is DerivedClass);  // True
Console.WriteLine(d is BaseClass);     // True
Console.WriteLine(d is BaseInterface); // True
Console.WriteLine(d is object);        // True
Console.WriteLine(d is string);        // False

var b = new BaseClass();
Console.WriteLine(b is DerivedClass);  // False
Console.WriteLine(b is BaseClass);     // True
Console.WriteLine(b is BaseInterface); // True
Console.WriteLine(b is object);        // True
Console.WriteLine(b is string);        // False

Si la intención de la conversión es usar el objeto, es una buena práctica usar la palabra clave as '

interface BaseInterface {}
class BaseClass : BaseInterface {}
class DerivedClass : BaseClass {}

var d = new DerivedClass();
Console.WriteLine(d is DerivedClass);  // True - valid use of 'is'
Console.WriteLine(d is BaseClass);     // True - valid use of 'is'

if(d is BaseClass){
    var castedD = (BaseClass)d;
    castedD.Method(); // valid, but not best practice
}

var asD = d as BaseClass;

if(asD!=null){
    asD.Method(); //prefered method since you incur only one unboxing penalty
}

Sin embargo, desde la característica de pattern matching C # 7 se extiende el operador is para verificar un tipo y declarar una nueva variable al mismo tiempo. Misma parte de código con C # 7:

7.0
if(d is BaseClass asD ){
    asD.Method();
}

tipo de

Devuelve el Type de un objeto, sin la necesidad de instanciarlo.

Type type = typeof(string);
Console.WriteLine(type.FullName); //System.String
Console.WriteLine("Hello".GetType() == type); //True
Console.WriteLine("Hello".GetType() == typeof(string)); //True

const

const se utiliza para representar valores que nunca cambiarán a lo largo de la vida útil del programa. Su valor es constante desde el tiempo de compilación , a diferencia de la palabra clave readonly , cuyo valor es constante desde el tiempo de ejecución.

Por ejemplo, dado que la velocidad de la luz nunca cambiará, podemos almacenarla en una constante.

const double c = 299792458;  // Speed of light

double CalculateEnergy(double mass)
{
    return mass * c * c;
}

Esto es esencialmente lo mismo que tener una return mass * 299792458 * 299792458 , ya que el compilador sustituirá directamente c con su valor constante.

Como resultado, c no se puede cambiar una vez declarado. Lo siguiente producirá un error en tiempo de compilación:

const double c = 299792458;  // Speed of light 

c = 500;  //compile-time error

Una constante se puede prefijar con los mismos modificadores de acceso que los métodos:

private const double c = 299792458;
public const double c = 299792458;
internal const double c = 299792458;

const miembros const son static por naturaleza. Sin embargo, el uso de static explícitamente no está permitido.

También puedes definir constantes locales de método:

double CalculateEnergy(double mass)
{
    const c = 299792458;
    return mass * c * c;
}

Estos no pueden ser prefijados con una palabra clave private o public , ya que son implícitamente locales al método en el que están definidos.


No todos los tipos se pueden utilizar en una declaración const . Los tipos de valores permitidos son los tipos predefinidos sbyte , byte , short , ushort , int , uint , long , ulong , char , float , double , decimal , bool y todos los tipos de enum . Intentar declarar miembros const con otros tipos de valor (como TimeSpan o Guid ) fallará en tiempo de compilación.

Para la string tipo de referencia predefinida especial, las constantes se pueden declarar con cualquier valor. Para todos los demás tipos de referencia, las constantes se pueden declarar, pero siempre deben tener el valor null .


Debido a que los valores const son conocidos en tiempo de compilación, se permiten como etiquetas de case en una declaración de switch , como argumentos estándar para parámetros opcionales, como argumentos para atribuir especificaciones, y así sucesivamente.


Si se utilizan valores const en diferentes ensamblajes, se debe tener cuidado con el control de versiones. Por ejemplo, si el ensamblaje A define una public const int MaxRetries = 3; , y el conjunto B usa esa constante, entonces si el valor de MaxRetries se cambia más tarde a 5 en el conjunto A (que luego se vuelve a compilar), ese cambio no será efectivo en el conjunto B a menos que el conjunto B también se vuelva a compilar (con Una referencia a la nueva versión de A).

Por esa razón, si un valor puede cambiar en futuras revisiones del programa, y ​​si el valor debe ser públicamente visible, no declare ese valor const menos que sepa que todos los conjuntos dependientes se volverán a compilar cada vez que se cambie algo. La alternativa es usar static readonly lugar de const , que se resuelve en tiempo de ejecución.

espacio de nombres

La palabra clave del namespace es una estructura de organización que nos ayuda a comprender cómo se organiza una base de código. Los espacios de nombres en C # son espacios virtuales en lugar de estar en una carpeta física.

namespace StackOverflow
{
    namespace Documentation
    {
        namespace CSharp.Keywords
        {
            public class Program
            {
                public static void Main()
                {
                    Console.WriteLine(typeof(Program).Namespace);
                    //StackOverflow.Documentation.CSharp.Keywords
                }
            }
        }
    }
}

Los espacios de nombres en C # también se pueden escribir en sintaxis encadenada. Lo siguiente es equivalente a lo anterior:

namespace StackOverflow.Documentation.CSharp.Keywords
{
    public class Program
    {
        public static void Main()
        {
            Console.WriteLine(typeof(Program).Namespace);
            //StackOverflow.Documentation.CSharp.Keywords
        }
    }
}

tratar, atrapar, finalmente, tirar

try , catch , finally y throw permite manejar excepciones en su código.

var processor = new InputProcessor();

// The code within the try block will be executed. If an exception occurs during execution of
// this code, execution will pass to the catch block corresponding to the exception type.
try 
{
    processor.Process(input);
}
// If a FormatException is thrown during the try block, then this catch block
// will be executed.
catch (FormatException ex)
{
    // Throw is a keyword that will manually throw an exception, triggering any catch block that is
    // waiting for that exception type. 
    throw new InvalidOperationException("Invalid input", ex);
}
// catch can be used to catch all or any specific exceptions. This catch block,
// with no type specified, catches any exception that hasn't already been caught
// in a prior catch block.
catch
{
    LogUnexpectedException(); 
    throw; // Re-throws the original exception.
}
// The finally block is executed after all try-catch blocks have been; either after the try has
// succeeded in running all commands or after all exceptions have been caught. 
finally
{
    processor.Dispose();
}

Nota: la palabra clave return se puede usar en el bloque try , y el bloque finally todavía se ejecutará (justo antes de regresar). Por ejemplo:

try 
{
    connection.Open();
    return connection.Get(query);
} 
finally 
{
    connection.Close();
}

La declaración connection.Close() se ejecutará antes de connection.Get(query) se devuelva el resultado de connection.Get(query) .

continuar

Inmediatamente pase el control a la siguiente iteración de la construcción de bucle envolvente (para, foreach, do, while):

for (var i = 0; i < 10; i++)
{
    if (i < 5)
    {
        continue;
    }
    Console.WriteLine(i);
}

Salida:

5
6
7
8
9

Demo en vivo en .NET Fiddle

var stuff = new [] {"a", "b", null, "c", "d"};

foreach (var s in stuff)
{
    if (s == null)
    {
        continue;
    }           
    Console.WriteLine(s);
}

Salida:

una
segundo
do
re

Demo en vivo en .NET Fiddle

ref, fuera

Las palabras clave ref y out hacen que un argumento se pase por referencia, no por valor. Para los tipos de valor, esto significa que el llamado puede cambiar el valor de la variable.

int x = 5;
ChangeX(ref x);
// The value of x could be different now

Para los tipos de referencia, la instancia en la variable no solo se puede modificar (como es el caso sin ref ), sino que también se puede reemplazar por completo:

Address a = new Address();
ChangeFieldInAddress(a);
// a will be the same instance as before, even if it is modified
CreateANewInstance(ref a);
// a could be an entirely new instance now

La principal diferencia entre las palabras clave out y ref es que ref requiere que la persona inicialice la variable, mientras que out pasa esa responsabilidad a la persona que llama.

Para usar un parámetro de out , tanto la definición del método como el método de llamada deben usar explícitamente la palabra clave de out .

int number = 1;
Console.WriteLine("Before AddByRef: " + number); // number = 1
AddOneByRef(ref number);
Console.WriteLine("After AddByRef: " + number);  // number = 2
SetByOut(out number);
Console.WriteLine("After SetByOut: " + number);  // number = 34

void AddOneByRef(ref int value)
{
    value++;
}

void SetByOut(out int value)
{
    value = 34;
}

Demo en vivo en .NET Fiddle

Lo siguiente no compila, porque out parámetros de out deben tener un valor asignado antes de que el método regrese (se compilaría usando ref ):

void PrintByOut(out int value)
{
    Console.WriteLine("Hello!");
}

usando la palabra clave out como modificador genérico

out palabra clave out también se puede utilizar en parámetros de tipo genérico al definir interfaces y delegados genéricos. En este caso, la palabra clave out especifica que el parámetro de tipo es covariante.

La covarianza le permite utilizar un tipo más derivado que el especificado por el parámetro genérico. Esto permite la conversión implícita de clases que implementan interfaces variantes y la conversión implícita de tipos de delegado. La covarianza y la contravarianza son compatibles con los tipos de referencia, pero no son compatibles con los tipos de valor. - MSDN

//if we have an interface like this
interface ICovariant<out R> { }

//and two variables like
ICovariant<Object> iobj = new Sample<Object>();
ICovariant<String> istr = new Sample<String>();

// then the following statement is valid
// without the out keyword this would have thrown error
iobj = istr; // implicit conversion occurs here

comprobado, sin marcar

Las palabras clave checked y unchecked checked definen cómo las operaciones manejan el desbordamiento matemático. "Desbordamiento" en el contexto de las palabras clave checked y unchecked checked es cuando una operación aritmética entera da como resultado un valor que es mayor en magnitud de lo que puede representar el tipo de datos objetivo.

Cuando se produce un desbordamiento dentro de un bloque checked (o cuando el compilador está configurado para usar aritmética comprobada globalmente), se lanza una excepción para advertir de un comportamiento no deseado. Mientras tanto, en un bloque unchecked marcar, el desbordamiento es silencioso: no se lanzan excepciones, y el valor simplemente se ajustará al límite opuesto. Esto puede llevar a errores sutiles y difíciles de encontrar.

Como la mayoría de las operaciones aritméticas se realizan en valores que no son lo suficientemente grandes o pequeños como para desbordarse, la mayoría de las veces, no es necesario definir explícitamente un bloque como checked . Se debe tener cuidado al realizar operaciones aritméticas en entradas no limitadas que pueden causar un desbordamiento, por ejemplo, cuando se realizan operaciones aritméticas en funciones recursivas o al recibir entradas del usuario.

Ni checked ni unchecked afecto flotante operaciones aritméticas de punto.

Cuando un bloque o expresión se declara como unchecked , cualquier operación aritmética dentro de él puede desbordarse sin causar un error. Un ejemplo en el que se desea este comportamiento sería el cálculo de una suma de comprobación, donde se permite que el valor se "ajuste" durante el cálculo:

byte Checksum(byte[] data) {
    byte result = 0;
    for (int i = 0; i < data.Length; i++) {
        result = unchecked(result + data[i]); // unchecked expression
    }
    return result;
}

Uno de los usos más comunes para unchecked es la implementación de un reemplazo personalizado para object.GetHashCode() , un tipo de suma de comprobación. Puede ver el uso de la palabra clave en las respuestas a esta pregunta: ¿Cuál es el mejor algoritmo para un System.Object.GetHashCode anulado? .

Cuando se declara que un bloque o expresión está checked , cualquier operación aritmética que cause un desbordamiento da lugar a que se OverflowException una OverflowException .

int SafeSum(int x, int y) {
    checked { // checked block
        return x + y; 
    }
}

Ambos marcados y sin marcar pueden estar en forma de bloque y expresión.

Los bloques marcados y no marcados no afectan los métodos llamados, solo los operadores llamados directamente en el método actual. Por ejemplo, Enum.ToObject() , Convert.ToInt32() , y los operadores definidos por el usuario no se ven afectados por los contextos personalizados marcados / no seleccionados.

Nota : El comportamiento predeterminado de desbordamiento predeterminado (marcado contra no seleccionado) puede cambiarse en las Propiedades del proyecto o mediante el interruptor de línea de comando / marcado [+ | -] . Es común predeterminar las operaciones comprobadas para las compilaciones de depuración y no verificadas para las compilaciones de lanzamiento. Las palabras clave checked y unchecked marcar se usarían entonces solo cuando el enfoque predeterminado no se aplique y usted necesite un comportamiento explícito para garantizar la corrección.

ir

goto se puede usar para saltar a una línea específica dentro del código, especificada por una etiqueta.

goto como a

Etiqueta:

void InfiniteHello()
{
    sayHello:
    Console.WriteLine("Hello!");
    goto sayHello;
}

Demo en vivo en .NET Fiddle

Declaración del caso:

enum Permissions { Read, Write };

switch (GetRequestedPermission())
{
    case Permissions.Read:
        GrantReadAccess();
        break;

    case Permissions.Write:
        GrantWriteAccess();
        goto case Permissions.Read; //People with write access also get read
}

Demo en vivo en .NET Fiddle

Esto es particularmente útil en la ejecución de múltiples comportamientos en una instrucción de conmutación, ya que C # no admite bloqueos de casos directos .

Reintento de excepción

var exCount = 0;
retry:
try
{
    //Do work
}
catch (IOException)
{
    exCount++;
    if (exCount < 3)
    {
        Thread.Sleep(100);
        goto retry;
    }
    throw;
}

Demo en vivo en .NET Fiddle

Al igual que en muchos idiomas, se desaconseja el uso de la palabra clave goto, excepto en los casos siguientes.

Usos válidos de goto que se aplican a C #:

  • Caso fallido en la declaración de cambio.

  • Descanso multinivel. LINQ a menudo se puede usar en su lugar, pero generalmente tiene un peor rendimiento.

  • Desasignación de recursos cuando se trabaja con objetos de bajo nivel no envueltos. En C #, los objetos de bajo nivel generalmente se deben envolver en clases separadas.

  • Máquinas de estados finitos, por ejemplo, analizadores; utilizado internamente por el compilador generado async / await máquinas de estado.

enumerar

La palabra clave enum le dice al compilador que esta clase hereda de la clase abstracta Enum , sin que el programador tenga que heredarla explícitamente. Enum es un descendiente de ValueType , que está diseñado para usarse con un conjunto distinto de constantes con nombre.

public enum DaysOfWeek
{
    Monday,
    Tuesday,
}

Opcionalmente, puede especificar un valor específico para cada uno (o algunos de ellos):

public enum NotableYear
{
   EndOfWwI = 1918;
   EnfOfWwII = 1945,
}

En este ejemplo, omití un valor para 0, esto suele ser una mala práctica. Una enum siempre tendrá un valor predeterminado producido por la conversión explícita (YourEnumType) 0 , donde YourEnumType es su tipo de enume declarado. Sin un valor de 0 definido, una enum no tendrá un valor definido al inicio.

El tipo subyacente predeterminado de enum es int , puede cambiar el tipo subyacente a cualquier tipo integral, incluidos byte , sbyte , short , ushort , int , uint , long y ulong . A continuación se muestra una enumeración con el byte tipo subyacente:

enum Days : byte
{
    Sunday = 0,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday
};

También tenga en cuenta que puede convertir a / desde el tipo subyacente simplemente con una conversión:

int value = (int)NotableYear.EndOfWwI;

Por estos motivos, es mejor que siempre compruebe si una enum es válida cuando expone las funciones de la biblioteca:

void PrintNotes(NotableYear year)
{
    if (!Enum.IsDefined(typeof(NotableYear), year))
        throw InvalidEnumArgumentException("year", (int)year, typeof(NotableYear));

    // ...
}

base

La palabra clave base se utiliza para acceder a los miembros de una clase base. Se usa comúnmente para llamar implementaciones base de métodos virtuales, o para especificar a qué constructor base se debe llamar.

Elegir un constructor

public class Child : SomeBaseClass {
    public Child() : base("some string for the base class")
    {
    }
}

public class SomeBaseClass {
    public SomeBaseClass()
    {
        // new Child() will not call this constructor, as it does not have a parameter
    }
    public SomeBaseClass(string message)
    {
        // new Child() will use this base constructor because of the specified parameter in Child's constructor
        Console.WriteLine(message);
    }
}

Llamando a la implementación base del método virtual.

public override void SomeVirtualMethod() {
    // Do something, then call base implementation
    base.SomeVirtualMethod();
}

Es posible utilizar la palabra clave base para llamar a una implementación base desde cualquier método. Esto vincula la llamada del método directamente a la implementación base, lo que significa que incluso si las nuevas clases secundarias anulan un método virtual, la implementación base se seguirá llamando, por lo que debe usarse con precaución.

public class Parent
{
    public virtual int VirtualMethod()
    {
        return 1;
    }
}

public class Child : Parent
{
    public override int VirtualMethod() {
        return 11;
    }

    public int NormalMethod()
    {
        return base.VirtualMethod();
    }

    public void CallMethods()
    {
        Assert.AreEqual(11, VirtualMethod());

        Assert.AreEqual(1, NormalMethod());
        Assert.AreEqual(1, base.VirtualMethod());
    }
}

public class GrandChild : Child
{
    public override int VirtualMethod()
    {
        return 21;
    }

    public void CallAgain()
    {
        Assert.AreEqual(21, VirtualMethod());
        Assert.AreEqual(11, base.VirtualMethod());

        // Notice that the call to NormalMethod below still returns the value
        // from the extreme base class even though the method has been overridden
        // in the child class.
        Assert.AreEqual(1, NormalMethod());
    }
}

para cada

foreach se utiliza para iterar sobre los elementos de una matriz o los elementos dentro de una colección que implementa IEnumerable ✝.

var lines = new string[] { 
    "Hello world!", 
    "How are you doing today?", 
    "Goodbye"
};

foreach (string line in lines)
{
    Console.WriteLine(line);
}

Esto dará salida

"¡Hola Mundo!"
"¿Cómo estás hoy?"
"Adiós"

Demo en vivo en .NET Fiddle

Puede salir del bucle foreach en cualquier momento utilizando la palabra clave break o pasar a la siguiente iteración utilizando la palabra clave continue .

var numbers = new int[] {1, 2, 3, 4, 5, 6};

foreach (var number in numbers)
{
    // Skip if 2
    if (number == 2)
        continue;

    // Stop iteration if 5
    if (number == 5)
        break;

    Console.Write(number + ", ");
}

// Prints: 1, 3, 4, 

Demo en vivo en .NET Fiddle

Tenga en cuenta que el orden de iteración está garantizado solo para ciertas colecciones como matrices y List , pero no está garantizado para muchas otras colecciones.


✝ Si bien IEnumerable se usa generalmente para indicar colecciones enumerables, foreach solo requiere que la colección exponga públicamente el método object GetEnumerator() del object GetEnumerator() , que debe devolver un objeto que expone el método bool MoveNext() y el object Current { get; } Propiedad.

params

params permite que un parámetro de método reciba un número variable de argumentos, es decir, se permiten cero, uno o varios argumentos para ese parámetro.

static int AddAll(params int[] numbers)
{
    int total = 0;
    foreach (int number in numbers)
    {
        total += number;
    }
    
    return total;
}

Ahora se puede llamar a este método con una lista típica de argumentos int , o una matriz de ints.

AddAll(5, 10, 15, 20);                // 50
AddAll(new int[] { 5, 10, 15, 20 });  // 50

params deben aparecer como máximo una vez y, si se utilizan, deben estar en último lugar en la lista de argumentos, incluso si el tipo posterior es diferente al de la matriz.


Tenga cuidado al sobrecargar las funciones cuando use la palabra clave params . C # prefiere hacer coincidir sobrecargas más específicas antes de recurrir a tratar de usar sobrecargas con params . Por ejemplo si tienes dos métodos:

static double Add(params double[] numbers)
{
    Console.WriteLine("Add with array of doubles");
    double total = 0.0;
    foreach (double number in numbers)
    {
        total += number;
    }
    
    return total;
}

static int Add(int a, int b)
{
    Console.WriteLine("Add with 2 ints");
    return a + b;
}

Luego, la sobrecarga específica de 2 argumentos tendrá prioridad antes de intentar la sobrecarga de params .

Add(2, 3);      //prints "Add with 2 ints"
Add(2, 3.0);    //prints "Add with array of doubles" (doubles are not ints)
Add(2, 3, 4);   //prints "Add with array of doubles" (no 3 argument overload)

descanso

En un bucle (for, foreach, do, while), la instrucción break la ejecución del bucle más interno y vuelve al código posterior. También se puede utilizar con un yield en el que se especifica que un iterador ha llegado a su fin.

for (var i = 0; i < 10; i++)
{
    if (i == 5)
    {
        break;
    }
    Console.WriteLine("This will appear only 5 times, as the break will stop the loop.");
}

Demo en vivo en .NET Fiddle

foreach (var stuff in stuffCollection)
{
    if (stuff.SomeStringProp == null)
        break;
    // If stuff.SomeStringProp for any "stuff" is null, the loop is aborted.
    Console.WriteLine(stuff.SomeStringProp);
}

La declaración de ruptura también se usa en construcciones de casos de conmutación para romper un caso o segmento predeterminado.

switch(a)
{
    case 5:
        Console.WriteLine("a was 5!");
        break;

    default:
        Console.WriteLine("a was something else!");
        break;
}

En las declaraciones de cambio, se requiere la palabra clave 'break' al final de cada declaración de caso. Esto es contrario a algunos idiomas que permiten "pasar" a la siguiente declaración de caso en la serie. Las soluciones para esto incluirían declaraciones 'goto' o apilar las declaraciones 'case' secuencialmente.

El siguiente código dará los números 0, 1, 2, ..., 9 y la última línea no se ejecutará. yield break significa el final de la función (no solo un bucle).

public static IEnumerable<int> GetNumbers()
{
    int i = 0;
    while (true) {
        if (i < 10) {
            yield return i++;
        } else {
            yield break;
        }
    }
    Console.WriteLine("This line will not be executed");
}

Demo en vivo en .NET Fiddle

Tenga en cuenta que, a diferencia de otros idiomas, no hay forma de etiquetar una ruptura particular en C #. Esto significa que en el caso de bucles anidados, solo se detendrá el bucle más interno:

foreach (var outerItem in outerList)
{
    foreach (var innerItem in innerList)
    {
        if (innerItem.ShoudBreakForWhateverReason)
            // This will only break out of the inner loop, the outer will continue:
            break; 
    }
}

Si quiere salir del bucle externo aquí, puede usar una de varias estrategias diferentes, como:

  • Una instrucción goto para saltar fuera de toda la estructura de bucle.
  • Una variable de shouldBreak específica ( shouldBreak en el siguiente ejemplo) que se puede verificar al final de cada iteración del bucle externo.
  • Refactorizando el código para usar una declaración de return en el cuerpo del bucle más interno, o evitar por completo la estructura del bucle anidado.
bool shouldBreak = false;
while(comeCondition)
{
    while(otherCondition)
    {
        if (conditionToBreak)
        {
            // Either tranfer control flow to the label below...
            goto endAllLooping;

            // OR use a flag, which can be checked in the outer loop:
            shouldBreak = true;
        }
    }

    if(shouldBreakNow)
    {
        break; // Break out of outer loop if flag was set to true
    }
}

endAllLooping: // label from where control flow will continue

resumen

Una clase marcada con la palabra clave abstract no puede ser instanciada.

Una clase debe marcarse como abstracta si contiene miembros abstractos o si hereda miembros abstractos que no implementa. Una clase puede marcarse como abstracta incluso si no hay miembros abstractos involucrados.

Las clases abstractas se usan generalmente como clases base cuando alguna parte de la implementación necesita ser especificada por otro componente.

abstract class Animal 
{
    string Name { get; set; }
    public abstract void MakeSound();
}

public class Cat : Animal 
{
    public override void MakeSound()
    {
        Console.WriteLine("Meov meov");
    }
}

public class Dog : Animal 
{   
    public override void MakeSound()
    {
        Console.WriteLine("Bark bark");
    }
}

Animal cat = new Cat();       // Allowed due to Cat deriving from Animal
cat.MakeSound();              // will print out "Meov meov"    

Animal dog = new Dog();       // Allowed due to Dog deriving from Animal
dog.MakeSound();              // will print out "Bark bark"

Animal animal = new Animal(); // Not allowed due to being an abstract class

Un método, propiedad o evento marcado con la palabra clave abstract indica que se espera que la implementación de ese miembro se proporcione en una subclase. Como se mencionó anteriormente, los miembros abstractos solo pueden aparecer en clases abstractas.

abstract class Animal 
{
   public abstract string Name { get; set; }
}

public class Cat : Animal 
{
    public override string Name { get; set; }
}

public class Dog : Animal 
{
    public override string Name { get; set; }
}

flotador, doble, decimal

flotador

float es un alias para el tipo de datos .NET System.Single . Permite almacenar los números de punto flotante de precisión simple IEEE 754. Este tipo de datos está presente en mscorlib.dll que todos los proyectos de C # hacen referencia implícitamente cuando los creas.

Rango aproximado: -3.4 × 10 38 a 3.4 × 10 38

Precisión decimal: 6-9 dígitos significativos

Notación

float f = 0.1259;
var f1 = 0.7895f; // f is literal suffix to represent float values 

Cabe señalar que el tipo de float menudo produce errores de redondeo significativos. En aplicaciones donde la precisión es importante, se deben considerar otros tipos de datos.


doble

double es un alias para el tipo de datos .NET System.Double . Representa un número de coma flotante de 64 bits de doble precisión. Este tipo de datos está presente en mscorlib.dll que se hace referencia implícitamente en cualquier proyecto de C #.

Rango: ± 5.0 × 10 −324 a ± 1.7 × 10 308

Precisión decimal: 15-16 dígitos significativos

Notación

double distance = 200.34; // a double value
double salary = 245; // an integer implicitly type-casted to double value
var marks = 123.764D; // D is literal suffix to represent double values

decimal

decimal es un alias para el tipo de datos .NET System.Decimal . Representa una palabra clave indica un tipo de datos de 128 bits. En comparación con los tipos de punto flotante, el tipo decimal tiene más precisión y un rango más pequeño, lo que lo hace apropiado para los cálculos financieros y monetarios. Este tipo de datos está presente en mscorlib.dll que se hace referencia implícitamente en cualquier proyecto de C #.

Rango: -7.9 × 10 28 a 7.9 × 10 28

Precisión decimal: 28-29 dígitos significativos

Notación

decimal payable = 152.25m; // a decimal value
var marks = 754.24m; // m is literal suffix to represent decimal values

uint

Un entero sin signo , o uint , es un tipo de datos numérico que solo puede contener enteros positivos. Como su nombre lo sugiere, representa un entero de 32 bits sin signo. La propia palabra clave uint es un alias para el tipo de sistema de tipo común System.UInt32 . Este tipo de datos está presente en mscorlib.dll , al que todos los proyectos de C # hacen referencia implícitamente cuando los creas. Ocupa cuatro bytes de espacio de memoria.

Los enteros sin signo pueden contener cualquier valor de 0 a 4,294,967,295.

Ejemplos de cómo y ahora no declarar enteros sin signo

uint i = 425697; // Valid expression, explicitly stated to compiler
var i1 = 789247U; // Valid expression, suffix allows compiler to determine datatype
uint x = 3.0; // Error, there is no implicit conversion

Tenga en cuenta: Según Microsoft , se recomienda utilizar el tipo de datos int siempre que sea posible, ya que el tipo de datos uint no es compatible con CLS.

esta

La palabra clave this refiere a la instancia actual de class (objeto). De esta manera, se pueden distinguir dos variables con el mismo nombre, una en el nivel de clase (un campo) y una que es un parámetro (o variable local) de un método.

public MyClass {
    int a;

    void set_a(int a)
    {
        //this.a refers to the variable defined outside of the method,
        //while a refers to the passed parameter.
        this.a = a;
    }
}

Otros usos de la palabra clave son el encadenamiento de sobrecargas de constructores no estáticos :

public MyClass(int arg) : this(arg, null)
{
}

y escritura de indexadores :

public string this[int idx1, string idx2]
{
    get { /* ... */ }
    set { /* ... */ }
}

y declarando métodos de extensión :

public static int Count<TItem>(this IEnumerable<TItem> source)
{
    // ...
}

Si no hay conflicto con una variable o parámetro local, es una cuestión de estilo si usar this o no, por lo que this.MemberOfType y MemberOfType serían equivalentes en ese caso. También vea la palabra clave base .

Tenga en cuenta que si se va a llamar a un método de extensión en la instancia actual, this es obligatorio. Por ejemplo, si está dentro de un método no estático de una clase que implementa IEnumerable<> y desea llamar al Count extensiones desde antes, debe usar:

this.Count()  // works like StaticClassForExtensionMethod.Count(this)

y this no puede ser omitido allí.

para

Sintaxis: for (initializer; condition; iterator)

  • El bucle for se usa comúnmente cuando se conoce el número de iteraciones.
  • Las declaraciones en la sección de initializer se ejecutan solo una vez, antes de ingresar al bucle.
  • La sección de condition contiene una expresión booleana que se evalúa al final de cada iteración de bucle para determinar si el bucle debería salir o debería ejecutarse de nuevo.
  • La sección del iterator define lo que sucede después de cada iteración del cuerpo del bucle.

Este ejemplo muestra cómo se puede usar for para iterar sobre los caracteres de una cadena:

string str = "Hello";
for (int i = 0; i < str.Length; i++)
{
    Console.WriteLine(str[i]);                
}

Salida:

H
mi
l
l
o

Demo en vivo en .NET Fiddle

Todas las expresiones que definen una sentencia for son opcionales; por ejemplo, la siguiente declaración se utiliza para crear un bucle infinito:

for( ; ; )
{
    // Your code here
}

La sección de initializer puede contener múltiples variables, siempre que sean del mismo tipo. La sección de condition puede consistir en cualquier expresión que pueda ser evaluada como un bool . Y la sección del iterator puede realizar múltiples acciones separadas por comas:

string hello = "hello";
for (int i = 0, j = 1, k = 9; i < 3 && k > 0; i++, hello += i) {
    Console.WriteLine(hello);
}

Salida:

Hola
hola1
hola12

Demo en vivo en .NET Fiddle

mientras

El operador while itera sobre un bloque de código hasta que la consulta condicional es falsa o el código se interrumpe con una goto , return , break o throw .

Sintaxis por palabra clave while :

while ( condición ) { bloque de código; }

Ejemplo:

int i = 0;
while (i++ < 5)
{
    Console.WriteLine("While is on loop number {0}.", i);
}

Salida:

"Mientras está en el bucle número 1."
"Mientras está en el bucle número 2."
"Mientras está en el bucle número 3."
"Mientras está en el bucle número 4."
"Mientras está en el bucle número 5."

Demo en vivo en .NET Fiddle

Un bucle while está controlado por entrada , ya que la condición se verifica antes de la ejecución del bloque de código adjunto. Esto significa que el bucle while no ejecutaría sus declaraciones si la condición es falsa.

bool a = false;

while (a == true)
{
    Console.WriteLine("This will never be printed.");
}

Darle una condición de while sin aprovisionarla para que se vuelva falso en algún punto resultará en un bucle infinito o infinito. En la medida de lo posible, esto debe evitarse, sin embargo, puede haber algunas circunstancias excepcionales cuando lo necesite.

Puede crear dicho bucle de la siguiente manera:

while (true)
{
//...
}

Tenga en cuenta que el compilador de C # transformará bucles como

while (true)
{
// ...
}

o

for(;;)
{
// ...
}

dentro

{
:label
// ...
goto label;
}

Tenga en cuenta que un bucle while puede tener cualquier condición, independientemente de su complejidad, siempre que se evalúe (o devuelva) un valor booleano (bool). También puede contener una función que devuelve un valor booleano (como una función de este tipo se evalúa al mismo tipo que una expresión como `a == x '). Por ejemplo,

while (AgriculturalService.MoreCornToPick(myFarm.GetAddress()))
{
    myFarm.PickCorn();
}

regreso

MSDN: la instrucción de retorno termina la ejecución del método en el que aparece y devuelve el control al método de llamada. También puede devolver un valor opcional. Si el método es un tipo nulo, se puede omitir la declaración de retorno.

public int Sum(int valueA, int valueB)
{
    return valueA + valueB;
}


public void Terminate(bool terminateEarly)
{
    if (terminateEarly) return; // method returns to caller if true was passed in
    else Console.WriteLine("Not early"); // prints only if terminateEarly was false
}

en

La palabra clave in tiene tres usos:

a) Como parte de la sintaxis en una declaración foreach o como parte de la sintaxis en una consulta LINQ

foreach (var member in sequence)
{
    // ...
}

b) En el contexto de las interfaces genéricas y los tipos de delegados genéricos significa la contravarianza para el parámetro de tipo en cuestión:

public interface IComparer<in T>
{
    // ...
}

c) En el contexto de la consulta LINQ se refiere a la colección que se está consultando

var query = from x in source select new { x.Name, x.ID, };

utilizando

Hay dos tipos de using palabras clave, using statement y using directive :

  1. utilizando declaración :

    La palabra clave de using garantiza que los objetos que implementan la interfaz IDisposable se eliminan correctamente después del uso. Hay un tema separado para la declaración de uso

  2. usando directiva

    La directiva de using tiene tres usos, vea la página msdn para la directiva de uso . Hay un tema separado para la directiva using .

sellado

Cuando se aplica a una clase, el modificador sealed evita que otras clases se hereden de ella.

class A { }
sealed class B : A { }
class C : B { } //error : Cannot derive from the sealed class

Cuando se aplica a un método virtual (o propiedad virtual), el modificador sealed evita que este método (propiedad) se invalide en las clases derivadas.

public class A 
{
    public sealed override string ToString() // Virtual method inherited from class Object
    {
        return "Do not override me!";
    }
}

public class B: A 
{
    public override string ToString() // Compile time error
    { 
        return "An attempt to override"; 
    }
}

tamaño de

Se utiliza para obtener el tamaño en bytes para un tipo no administrado

int byteSize = sizeof(byte) // 1
int sbyteSize = sizeof(sbyte) // 1
int shortSize = sizeof(short) // 2
int ushortSize = sizeof(ushort) // 2
int intSize = sizeof(int) // 4
int uintSize = sizeof(uint) // 4
int longSize = sizeof(long) // 8
int ulongSize = sizeof(ulong) // 8
int charSize = sizeof(char) // 2(Unicode)
int floatSize = sizeof(float) // 4
int doubleSize = sizeof(double) // 8
int decimalSize = sizeof(decimal) // 16
int boolSize = sizeof(bool) // 1

estático

El modificador static se usa para declarar un miembro estático, que no necesita ser instanciado para poder acceder, sino que se accede a él simplemente a través de su nombre, es decir, DateTime.Now .

static se puede usar con clases, campos, métodos, propiedades, operadores, eventos y constructores.

Mientras que una instancia de una clase contiene una copia separada de todos los campos de instancia de la clase, solo hay una copia de cada campo estático.

class A
{
    static public int count = 0;

    public A()
    {
        count++;
    }
}

class Program
{
    static void Main(string[] args)
    {
        A a = new A();
        A b = new A();
        A c = new A();

        Console.WriteLine(A.count); // 3 
    }
}

count es igual al número total de instancias de A clase.

El modificador estático también se puede usar para declarar un constructor estático para una clase, para inicializar datos estáticos o ejecutar código que solo necesita ser llamado una vez. Los constructores estáticos se llaman antes de que se haga referencia a la clase por primera vez.

class A
{
    static public DateTime InitializationTime;

    // Static constructor
    static A()
    {
        InitializationTime = DateTime.Now;
        // Guaranteed to only run once
        Console.WriteLine(InitializationTime.ToString());
    }
}

Una static class está marcada con la palabra clave static y puede usarse como un contenedor beneficioso para un conjunto de métodos que funcionan con parámetros, pero que no necesariamente requieren estar vinculados a una instancia. Debido a la naturaleza static de la clase, no se puede crear una instancia, pero puede contener un static constructor . Algunas características de una static class incluyen:

  • No puede ser heredado
  • No se puede heredar de otra cosa que no sea Object
  • Puede contener un constructor estático pero no un constructor de instancia
  • Solo puede contener miembros estáticos
  • Está sellado

El compilador también es amigable y le permitirá al desarrollador saber si existen miembros de la instancia dentro de la clase. Un ejemplo sería una clase estática que convierte entre métricas de EE. UU. Y Canadá:

static class ConversionHelper {
    private static double oneGallonPerLitreRate = 0.264172;

    public static double litreToGallonConversion(int litres) {
        return litres * oneGallonPerLitreRate;
    }
}

Cuando las clases son declaradas estáticas:

public static class Functions
{
  public static int Double(int value)
  {
    return value + value;
  }
}

Todas las funciones, propiedades o miembros dentro de la clase también deben declararse estáticas. No se puede crear ninguna instancia de la clase. En esencia, una clase estática le permite crear paquetes de funciones que se agrupan de forma lógica.

Dado que C # 6 static también se puede usar junto con el using para importar miembros y métodos estáticos. Se pueden usar luego sin nombre de clase.

Manera antigua, sin using static :

using System;

public class ConsoleApplication
{
    public static void Main()
    {
         Console.WriteLine("Hello World!"); //Writeline is method belonging to static class Console
    }

}

Ejemplo con el using static

using static System.Console;

public class ConsoleApplication
{
    public static void Main()
    {
         WriteLine("Hello World!"); //Writeline is method belonging to static class Console
    }

}

Inconvenientes

Si bien las clases estáticas pueden ser increíblemente útiles, vienen con sus propias advertencias:

  • Una vez que se ha llamado a la clase estática, la clase se carga en la memoria y no se puede ejecutar a través del recolector de basura hasta que se descargue el AppDomain que contiene la clase estática.

  • Una clase estática no puede implementar una interfaz.

En t

int es un alias para System.Int32 , que es un tipo de datos para enteros de 32 bits con signo. Este tipo de datos se puede encontrar en mscorlib.dll cual todos los proyectos de C # hacen referencia implícitamente cuando los creas.

Rango: -2,147,483,648 a 2,147,483,647

int int1 = -10007;
var int2 = 2132012521;     

largo

La palabra clave larga se utiliza para representar enteros de 64 bits con signo. Es un alias para el tipo de datos System.Int64 presente en mscorlib.dll , al que todos los proyectos de C # hacen referencia implícitamente al crearlos.

Cualquier variable larga se puede declarar explícita e implícitamente:

long long1 = 9223372036854775806;  // explicit declaration, long keyword used
var long2 = -9223372036854775806L; // implicit declaration, 'L' suffix used

Una variable larga puede contener cualquier valor desde –9,223,372,036,854,775,808 a 9,223,372,036,854,775,807, y puede ser útil en situaciones en que una variable debe tener un valor que exceda los límites de lo que pueden contener otras variables (como la variable int ).

ulong

Palabra clave utilizada para enteros de 64 bits sin signo. Representa el tipo de datos System.UInt64 que se encuentra en mscorlib.dll que se hace referencia implícitamente en todos los proyectos de C # cuando los crea.

Rango: 0 a 18,446,744,073,709,551,615

ulong veryLargeInt = 18446744073609451315;
var anotherVeryLargeInt = 15446744063609451315UL;

dinámica

La palabra clave dynamic se utiliza con objetos tipificados dinámicamente . Los objetos declarados como dynamic renuncian a las verificaciones estáticas en tiempo de compilación y, en cambio, se evalúan en tiempo de ejecución.

using System;
using System.Dynamic;

dynamic info = new ExpandoObject();
info.Id = 123;
info.Another = 456;

Console.WriteLine(info.Another);
// 456

Console.WriteLine(info.DoesntExist);
// Throws RuntimeBinderException

El siguiente ejemplo usa dynamic con la biblioteca Json.NET de Newtonsoft, para leer fácilmente los datos de un archivo JSON deserializado.

try
{
    string json = @"{ x : 10, y : ""ho""}";
    dynamic deserializedJson = JsonConvert.DeserializeObject(json);
    int x = deserializedJson.x;
    string y = deserializedJson.y;
    // int z = deserializedJson.z; // throws RuntimeBinderException
}
catch (RuntimeBinderException e)
{
    // This exception is thrown when a property
    // that wasn't assigned to a dynamic variable is used
}

Hay algunas limitaciones asociadas con la palabra clave dinámica. Uno de ellos es el uso de métodos de extensión. El siguiente ejemplo agrega un método de extensión para la cadena: SayHello .

static class StringExtensions
{
    public static string SayHello(this string s) => $"Hello {s}!";
}

El primer enfoque será llamarlo como de costumbre (como para una cadena):

var person = "Person";
Console.WriteLine(person.SayHello());

dynamic manager = "Manager";
Console.WriteLine(manager.SayHello()); // RuntimeBinderException

No hay error de compilación, pero en tiempo de ejecución obtiene una RuntimeBinderException . La solución para esto será llamar al método de extensión a través de la clase estática:

var helloManager = StringExtensions.SayHello(manager);
Console.WriteLine(helloManager);

virtual, anular, nuevo

virtual y anular

La palabra clave virtual permite que un método, una propiedad, un indexador o un evento sean anulados por clases derivadas y presente comportamiento polimórfico. (Los miembros son no virtuales por defecto en C #)

public class BaseClass
{
    public virtual void Foo()
    {
        Console.WriteLine("Foo from BaseClass");
    }
}

Para anular un miembro, la palabra clave de override se utiliza en las clases derivadas. (Note que la firma de los miembros debe ser idéntica)

public class DerivedClass: BaseClass
{
    public override void Foo()
    {
        Console.WriteLine("Foo from DerivedClass");
    }
}

El comportamiento polimórfico de los miembros virtuales significa que cuando se invoca, el miembro real que se está ejecutando se determina en tiempo de ejecución en lugar de en tiempo de compilación. El miembro que prevalece en la clase más derivada del cual el objeto particular es una instancia será el ejecutado.

En resumen, el objeto se puede declarar del tipo BaseClass en tiempo de compilación, pero si en tiempo de ejecución es una instancia de DerivedClass , el miembro anulado se ejecutará:

BaseClass obj1 = new BaseClass();
obj1.Foo(); //Outputs "Foo from BaseClass"

obj1 = new DerivedClass();
obj1.Foo(); //Outputs "Foo from DerivedClass"    

Anular un método es opcional:

public class SecondDerivedClass: DerivedClass {}

var obj1 = new SecondDerivedClass();
obj1.Foo(); //Outputs "Foo from DerivedClass"    

nuevo

Dado que solo los miembros definidos como virtual son reemplazables y polimórficos, una clase derivada que redefine un miembro no virtual podría generar resultados inesperados.

public class BaseClass
{
    public void Foo()
    {
        Console.WriteLine("Foo from BaseClass");
    }
}

public class DerivedClass: BaseClass
{
    public void Foo()
    {
        Console.WriteLine("Foo from DerivedClass");
    }
}

BaseClass obj1 = new BaseClass();
obj1.Foo(); //Outputs "Foo from BaseClass"

obj1 = new DerivedClass();
obj1.Foo(); //Outputs "Foo from BaseClass" too!    

Cuando esto sucede, el miembro ejecutado siempre se determina en el momento de la compilación en función del tipo de objeto.

  • Si el objeto se declara de tipo BaseClass (incluso si el tiempo de ejecución es de una clase derivada), se ejecuta el método de BaseClass
  • Si el objeto se declara de tipo DerivedClass entonces se DerivedClass el método de DerivedClass .

Esto suele ser un accidente (cuando se agrega un miembro al tipo base después de que se agregó uno idéntico al tipo derivado) y se genera una advertencia del compilador CS0108 en esos escenarios.

Si fue intencional, entonces la new palabra clave se usa para suprimir la advertencia del compilador (¡e informar a otros desarrolladores de sus intenciones!). el comportamiento sigue siendo el mismo, la new palabra clave simplemente suprime la advertencia del compilador.

public class BaseClass
{
    public void Foo()
    {
        Console.WriteLine("Foo from BaseClass");
    }
}

public class DerivedClass: BaseClass
{
    public new void Foo()
    {
        Console.WriteLine("Foo from DerivedClass");
    }
}

BaseClass obj1 = new BaseClass();
obj1.Foo(); //Outputs "Foo from BaseClass"

obj1 = new DerivedClass();
obj1.Foo(); //Outputs "Foo from BaseClass" too! 

El uso de anulación no es opcional

A diferencia de C ++, el uso de la palabra clave de override no es opcional:

public class A
{
    public virtual void Foo()
    {
    }
}

public class B : A
{
    public void Foo() // Generates CS0108
    {
    }
}

El ejemplo anterior también provoca la advertencia CS0108 , porque B.Foo() no reemplaza automáticamente a A.Foo() . Agregue la override cuando la intención sea anular la clase base y cause un comportamiento polimórfico, agregue una new cuando desee un comportamiento no polimórfico y resuelva la llamada utilizando el tipo estático. Este último debe usarse con precaución, ya que puede causar una confusión grave.

El siguiente código incluso resulta en un error:

public class A
{
    public void Foo()
    {
    }
}

public class B : A
{
    public override void Foo() // Error: Nothing to override
    {
    }
}

Las clases derivadas pueden introducir polimorfismo.

El siguiente código es perfectamente válido (aunque raro):

    public class A
    {
        public void Foo()
        {
            Console.WriteLine("A");
        }
    }

    public class B : A
    {
        public new virtual void Foo() 
        {
            Console.WriteLine("B");
        }
    }

Ahora todos los objetos con una referencia estática de B (y sus derivados) usan polimorfismo para resolver Foo() , mientras que las referencias de A usan A.Foo() .

A a = new A();
a.Foo(); // Prints "A";
a = new B();
a.Foo(); // Prints "A";
B b = new B();
b.Foo(); // Prints "B";

Los métodos virtuales no pueden ser privados.

El compilador de C # es estricto en la prevención de construcciones sin sentido. Los métodos marcados como virtual no pueden ser privados. Debido a que un método privado no se puede ver desde un tipo derivado, tampoco se puede sobrescribir. Esto no puede compilar:

public class A
{
    private virtual void Foo() // Error: virtual methods cannot be private
    {
    }
}

asíncrono, espera

La palabra clave await se agregó como parte de la versión C # 5.0 que se admite desde Visual Studio 2012 en adelante. Aprovecha la biblioteca paralela de tareas (TPL) que hizo que el subprocesamiento múltiple sea relativamente más fácil. Las palabras clave async y await se utilizan en pares en la misma función que se muestra a continuación. La palabra clave await se utiliza para pausar la ejecución del método asíncrono actual hasta que se complete la tarea asíncrona esperada y / o se devuelvan sus resultados. Para utilizar la palabra clave await , el método que la usa debe estar marcado con la palabra clave async .

Se desaconseja fuertemente el uso de async con void . Para más información podéis consultar aquí .

Ejemplo:

public async Task DoSomethingAsync()
{    
    Console.WriteLine("Starting a useless process...");
    Stopwatch stopwatch = Stopwatch.StartNew();
    int delay = await UselessProcessAsync(1000);
    stopwatch.Stop();
    Console.WriteLine("A useless process took {0} milliseconds to execute.", stopwatch.ElapsedMilliseconds);
}

public async Task<int> UselessProcessAsync(int x)
{
    await Task.Delay(x);
    return x;
}

Salida:

"Comenzando un proceso inútil ..."

** ... 1 segundo de retraso ... **

"Un proceso inútil tomó 1000 milisegundos para ejecutarse".

Los pares de palabras clave async y await pueden omitirse si un método de devolución de Task o Task<T> solo devuelve una sola operación asíncrona.

En vez de esto:

public async Task PrintAndDelayAsync(string message, int delay)
{
    Debug.WriteLine(message);
    await Task.Delay(x);
}

Se prefiere hacer esto:

public Task PrintAndDelayAsync(string message, int delay)
{
    Debug.WriteLine(message);
    return Task.Delay(x);
}
5.0

En C # 5.0, la await no se puede usar en catch y finally .

6.0

Con C # 6.0 await se puede usar en catch y finally .

carbonizarse

Un char es una sola letra almacenada dentro de una variable. Es un tipo de valor incorporado que ocupa dos bytes de espacio de memoria. Representa el tipo de datos System.Char que se encuentra en mscorlib.dll que todos los proyectos de C # hacen referencia implícitamente cuando los creas.

Hay varias formas de hacer esto.

  1. char c = 'c';
  2. char c = '\u0063'; //Unicode
  3. char c = '\x0063'; //Hex
  4. char c = (char)99;//Integral

Un char puede convertirse implícitamente en ushort, int, uint, long, ulong, float, double, o decimal y devolverá el valor entero de ese char.

ushort u = c;

devuelve 99 etc.

Sin embargo, no hay conversiones implícitas de otros tipos a char. En su lugar debes lanzarlos.

ushort u = 99;
 char c = (char)u;

bloquear

lock proporciona seguridad para subprocesos para un bloque de código, de modo que solo un subproceso puede acceder a él dentro del mismo proceso. Ejemplo:

private static object _lockObj = new object();
static void Main(string[] args)
{
    Task.Run(() => TaskWork());
    Task.Run(() => TaskWork());
    Task.Run(() => TaskWork());

    Console.ReadKey();
}

private static void TaskWork()
{
    lock(_lockObj)
    {
        Console.WriteLine("Entered");

        Task.Delay(3000);
        Console.WriteLine("Done Delaying");

        // Access shared resources safely

        Console.WriteLine("Leaving");
    }   
}

Output:

Entered
Done Delaying
Leaving
Entered
Done Delaying
Leaving
Entered
Done Delaying
Leaving

Casos de uso:

Siempre que tenga un bloque de código que pueda producir efectos secundarios si se ejecuta por varios subprocesos al mismo tiempo. La palabra clave de bloqueo junto con un objeto de sincronización compartido ( _objLock en el ejemplo) se puede usar para evitar eso.

Tenga en cuenta que _objLock no puede ser null y que varios subprocesos que ejecutan el código deben usar la misma instancia de objeto (ya sea convirtiéndolo en un campo static o usando la misma instancia de clase para ambos subprocesos)

Desde el lado del compilador, la palabra clave de bloqueo es un azúcar sintáctico que se reemplaza por Monitor.Enter(_lockObj); y Monitor.Exit(_lockObj); . Entonces, si reemplaza el bloqueo rodeando el bloque de código con estos dos métodos, obtendría los mismos resultados. Puede ver el código real en Azúcar sintáctica en C #: ejemplo de bloqueo

nulo

Una variable de un tipo de referencia puede contener una referencia válida a una instancia o una referencia nula. La referencia nula es el valor predeterminado de las variables de tipo de referencia, así como los tipos de valor que admiten valores nulos.

null es la palabra clave que representa una referencia nula.

Como expresión, se puede utilizar para asignar la referencia nula a las variables de los tipos mencionados:

object a = null;
string b = null;
int? c = null;
List<int> d  = null;

A los tipos de valores no anulables no se les puede asignar una referencia nula. Todas las siguientes asignaciones son inválidas:

int a = null; 
float b = null;
decimal c = null;

La referencia nula no debe confundirse con instancias válidas de varios tipos, tales como:

  • una lista vacía ( new List<int>() )
  • una cadena vacía ( "" )
  • el número cero ( 0 , 0f , 0m )
  • el carácter nulo ( '\0' )

A veces, es significativo verificar si algo es nulo o un objeto vacío / predeterminado. El método System.String.IsNullOrEmpty (String) puede usarse para verificar esto, o puede implementar su propio método equivalente.

private void GreetUser(string userName)
{
    if (String.IsNullOrEmpty(userName))
    {
        //The method that called us either sent in an empty string, or they sent us a null reference. Either way, we need to report the problem.
        throw new InvalidOperationException("userName may not be null or empty.");
    }
    else
    {
        //userName is acceptable.
        Console.WriteLine("Hello, " + userName + "!");
    }
}

interno

La palabra clave internal es un modificador de acceso para tipos y miembros de tipo. Los tipos internos o miembros son accesibles solo dentro de los archivos en el mismo ensamblaje

uso:

public class BaseClass 
{
    // Only accessible within the same assembly
    internal static int x = 0;
}

La diferencia entre los diferentes modificadores de acceso se aclara aquí.

Modificadores de acceso

público

Se puede acceder al tipo o al miembro mediante cualquier otro código en el mismo conjunto u otro conjunto que lo haga referencia.

privado

Solo se puede acceder al tipo o miembro por código en la misma clase o estructura.

protegido

Solo se puede acceder al tipo o miembro por código en la misma clase o estructura, o en una clase derivada.

interno

Se puede acceder al tipo o miembro mediante cualquier código en el mismo ensamblaje, pero no desde otro ensamblaje.

protegido interno

Se puede acceder al tipo o miembro mediante cualquier código en el mismo ensamblaje, o mediante cualquier clase derivada en otro ensamblaje.

Cuando no se establece ningún modificador de acceso, se utiliza un modificador de acceso predeterminado. Por lo tanto, siempre hay algún tipo de modificador de acceso, incluso si no está configurado.

dónde

where puede servir dos propósitos en C #: restringir el tipo en un argumento genérico y filtrar consultas LINQ.

En una clase genérica, consideremos

public class Cup<T>
{
    // ...
}

T se llama un parámetro de tipo. La definición de clase puede imponer restricciones en los tipos reales que se pueden suministrar para T.

Se pueden aplicar los siguientes tipos de restricciones:

  • tipo de valor
  • tipo de referencia
  • Constructor predeterminado
  • herencia e implementación

tipo de valor

En este caso, solo se pueden suministrar struct (esto incluye tipos de datos 'primitivos' como int , boolean , etc.)

public class Cup<T> where T : struct
{
    // ...
}

tipo de referencia

En este caso solo se pueden suministrar tipos de clase.

public class Cup<T> where T : class
{
    // ...
}

valor híbrido / tipo de referencia

Ocasionalmente, se desea restringir los argumentos de tipo a los disponibles en una base de datos, y estos generalmente se asignan a tipos de valor y cadenas. Como todas las restricciones de tipo deben cumplirse, no es posible especificar where T : struct or string (esto no es una sintaxis válida). Una solución es restringir los argumentos de tipo a IConvertible que ha incorporado tipos de "... Boolean, SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double, Decimal, DateTime, Char y String. " Es posible que otros objetos implementen IConvertible, aunque esto es raro en la práctica.

public class Cup<T> where T : IConvertible
{
    // ...
}

Constructor predeterminado

Solo se permitirán los tipos que contengan un constructor por defecto. Esto incluye tipos de valor y clases que contienen un constructor predeterminado (sin parámetros)

public class Cup<T> where T : new
{
    // ...
}

herencia e implementación

Solo se pueden suministrar los tipos que heredan de una determinada clase base o implementan una interfaz determinada.

public class Cup<T> where T : Beverage
{
    // ...
}


public class Cup<T> where T : IBeer
{
    // ...
}

La restricción puede incluso hacer referencia a otro parámetro de tipo:

public class Cup<T, U> where U : T
{
    // ...
}

Se pueden especificar múltiples restricciones para un argumento de tipo:

public class Cup<T> where T : class, new()
{
    // ...
}

Los ejemplos anteriores muestran restricciones genéricas en una definición de clase, pero las restricciones se pueden usar en cualquier lugar donde se proporcione un argumento de tipo: clases, estructuras, interfaces, métodos, etc.

where también puede haber una cláusula LINQ. En este caso es análogo a WHERE en SQL:

int[] nums = { 5, 2, 1, 3, 9, 8, 6, 7, 2, 0 };

var query =
    from num in nums 
    where num < 5
    select num;

    foreach (var n in query)
    {
        Console.Write(n + " ");
    }
    // prints 2 1 3 2 0

externo

La palabra clave extern se utiliza para declarar métodos que se implementan externamente. Esto se puede usar junto con el atributo DllImport para llamar al código no administrado usando los servicios de Interop. que en este caso vendrá con modificador static

Por ejemplo:

using System.Runtime.InteropServices;
public class MyClass
{
    [DllImport("User32.dll")]
    private static extern int SetForegroundWindow(IntPtr point);

    public void ActivateProcessWindow(Process p)
    {
        SetForegroundWindow(p.MainWindowHandle);
    }
}

Esto utiliza el método SetForegroundWindow importado de la biblioteca User32.dll

Esto también se puede utilizar para definir un alias de ensamblaje externo. Lo que nos permite hacer referencia a diferentes versiones de los mismos componentes de un solo conjunto.

Para hacer referencia a dos ensamblajes con los mismos nombres de tipo completamente calificados, se debe especificar un alias en el símbolo del sistema, de la siguiente manera:

/r:GridV1=grid.dll
/r:GridV2=grid20.dll

Esto crea los alias externos GridV1 y GridV2. Para usar estos alias dentro de un programa, haga referencia a ellos usando la palabra clave extern. Por ejemplo:

extern alias GridV1;
extern alias GridV2;

bool

Palabra clave para almacenar los valores booleanos true y false . bool es un alias de System.Boolean.

El valor predeterminado de un bool es falso.

bool b; // default value is false
b = true; // true
b = ((5 + 2) == 6); // false

Para que un bool permita valores nulos, debe inicializarse como un bool ?.

El valor por defecto de un bool? es nulo.

bool? a // default value is null

cuando

El when es una palabra clave agregada en C # 6 , y se usa para el filtrado de excepciones.

Antes de la introducción de la palabra clave when , podría haber tenido una cláusula catch para cada tipo de excepción; con la adición de la palabra clave, ahora es posible un control más preciso.

A when expresión se adjunta a una rama catch , y solo si la condición when es true , se ejecutará la cláusula catch . Es posible tener varias cláusulas catch con los mismos tipos de clase de excepción y diferentes when condiciones.

private void CatchException(Action action)
{
    try
    {
        action.Invoke();
    }
    
    // exception filter
    catch (Exception ex) when (ex.Message.Contains("when"))
    {
        Console.WriteLine("Caught an exception with when");
    }

    catch (Exception ex)
    {
        Console.WriteLine("Caught an exception without when");
    }
}

private void Method1() { throw new Exception("message for exception with when"); }
private void Method2() { throw new Exception("message for general exception"); }


CatchException(Method1);
CatchException(Method2);

desenfrenado

La palabra clave unchecked evita que el compilador compruebe desbordamientos / subdesbordos.

Por ejemplo:

const int ConstantMax = int.MaxValue;
unchecked
{
    int1 = 2147483647 + 10;
}
int1 = unchecked(ConstantMax + 10);

Sin la palabra clave unchecked marcar, ninguna de las dos operaciones de adición se compilará.

¿Cuándo es esto útil?

Esto es útil ya que puede ayudar a acelerar los cálculos que definitivamente no se desbordarán ya que la verificación del desbordamiento lleva tiempo, o cuando se desea un comportamiento de desbordamiento / subdesbordamiento (por ejemplo, al generar un código hash).

vacío

La palabra reservada "void" es un alias de tipo System.Void , y tiene dos usos:

  1. Declare un método que no tiene un valor de retorno:
public void DoSomething()
{
    // Do some work, don't return any value to the caller.
}

Un método con un tipo de retorno de vacío todavía puede tener la palabra clave de return en su cuerpo. Esto es útil cuando desea salir de la ejecución del método y devolver el flujo a la persona que llama:

public void DoSomething()
{
    // Do some work...

    if (condition)
        return;

    // Do some more work if the condition evaluated to false.
}
  1. Declare un puntero a un tipo desconocido en un contexto inseguro.

En un contexto inseguro, un tipo puede ser un tipo de puntero, un tipo de valor o un tipo de referencia. Una declaración de tipo de puntero suele ser type* identifier , donde el tipo es un tipo conocido, es decir, int* myInt , pero también puede ser void* identifier , donde el tipo es desconocido.

Tenga en cuenta que Microsoft no recomienda declarar un tipo de puntero nulo .

si, si ... más, si ... más si


La sentencia if se usa para controlar el flujo del programa. Una sentencia if identifica qué sentencia ejecutar según el valor de una expresión Boolean .

Para una sola declaración, las braces {} son opcionales pero se recomiendan.

int a = 4;
if(a % 2 == 0) 
{
     Console.WriteLine("a contains an even number");
}
// output: "a contains an even number"

El if también puede tener una cláusula else , que se ejecutará en caso de que la condición se evalúe como falsa:

int a = 5;
if(a % 2 == 0) 
{
     Console.WriteLine("a contains an even number");
}
else
{
     Console.WriteLine("a contains an odd number");
}
// output: "a contains an odd number"

La construcción if ... else if permite especificar múltiples condiciones:

int a = 9;
if(a % 2 == 0) 
{
     Console.WriteLine("a contains an even number");
}
else if(a % 3 == 0) 
{
     Console.WriteLine("a contains an odd number that is a multiple of 3"); 
}
else
{
     Console.WriteLine("a contains an odd number");
}
// output: "a contains an odd number that is a multiple of 3"

Es importante tener en cuenta que si se cumple una condición en el ejemplo anterior, el control omite otras pruebas y salta al final de esa construcción particular. De lo contrario, el orden de las pruebas es importante si está utilizando if ... else if construct

Las expresiones booleanas de C # utilizan la evaluación de cortocircuito . Esto es importante en los casos en que las condiciones de evaluación pueden tener efectos secundarios:

if (someBooleanMethodWithSideEffects() && someOtherBooleanMethodWithSideEffects()) {
  //...
}

No hay garantía de que se someOtherBooleanMethodWithSideEffects realmente algún otro someOtherBooleanMethodWithSideEffects con someOtherBooleanMethodWithSideEffects .

También es importante en los casos en que las condiciones anteriores aseguran que es "seguro" evaluar las posteriores. Por ejemplo:

if (someCollection != null && someCollection.Count > 0) {
   // ..
}

El orden es muy importante en este caso porque, si revertimos el orden:

if (someCollection.Count > 0 && someCollection != null) {

lanzará una NullReferenceException si someCollection es null .

hacer

El operador do itera sobre un bloque de código hasta que una consulta condicional es igual a falso. El bucle do-while también puede ser interrumpido por una goto , return , break o throw .

La sintaxis de la palabra clave do es:

hacer { bloque de código; } while ( condición );

Ejemplo:

int i = 0;

do
{
    Console.WriteLine("Do is on loop number {0}.", i);
} while (i++ < 5);

Salida:

"Do está en el bucle número 1."
"Do está en el bucle número 2."
"Do está en el bucle número 3."
"Do está en el bucle número 4."
"Do está en el bucle número 5".

A diferencia del while de bucle, el bucle do-while es la salida controlada. Esto significa que el bucle do-while ejecutaría sus declaraciones al menos una vez, incluso si la condición falla la primera vez.

bool a = false;

do
{
    Console.WriteLine("This will be printed once, even if a is false.");
} while (a == true);

operador

La mayoría de los operadores integrados (incluidos los operadores de conversión) se pueden sobrecargar utilizando la palabra clave del operator junto con los modificadores public y static .

Los operadores se presentan en tres formas: operadores unarios, operadores binarios y operadores de conversión.

Los operadores unarios y binarios requieren al menos un parámetro del mismo tipo que el tipo que contiene, y algunos requieren un operador coincidente complementario.

Los operadores de conversión deben convertir hacia o desde el tipo adjunto.

public struct Vector32
{
    
    public Vector32(int x, int y)
    {
        X = x;
        Y = y;
    }
    
    public int X { get; }
    public int Y { get; }

    public static bool operator ==(Vector32 left, Vector32 right)
        => left.X == right.X && left.Y == right.Y;

    public static bool operator !=(Vector32 left, Vector32 right)
        => !(left == right);

    public static Vector32 operator +(Vector32 left, Vector32 right)
        => new Vector32(left.X + right.X, left.Y + right.Y);

    public static Vector32 operator +(Vector32 left, int right)
        => new Vector32(left.X + right, left.Y + right);

    public static Vector32 operator +(int left, Vector32 right)
        => right + left;

    public static Vector32 operator -(Vector32 left, Vector32 right)
        => new Vector32(left.X - right.X, left.Y - right.Y);

    public static Vector32 operator -(Vector32 left, int right)
        => new Vector32(left.X - right, left.Y - right);

    public static Vector32 operator -(int left, Vector32 right)
        => right - left;

    public static implicit operator Vector64(Vector32 vector)
        => new Vector64(vector.X, vector.Y);

    public override string ToString() => $"{{{X}, {Y}}}";

}

public struct Vector64
{

    public Vector64(long x, long y)
    {
        X = x;
        Y = y;
    }

    public long X { get; }
    public long Y { get; }

    public override string ToString() => $"{{{X}, {Y}}}";

}

Ejemplo

var vector1 = new Vector32(15, 39);
var vector2 = new Vector32(87, 64);
        
Console.WriteLine(vector1 == vector2); // false
Console.WriteLine(vector1 != vector2); // true
Console.WriteLine(vector1 + vector2);  // {102, 103}
Console.WriteLine(vector1 - vector2);  // {-72, -25}

estructura

Un tipo de struct es un tipo de valor que normalmente se usa para encapsular pequeños grupos de variables relacionadas, como las coordenadas de un rectángulo o las características de un artículo en un inventario.

Las clases son tipos de referencia, las estructuras son tipos de valor.

using static System.Console;

namespace ConsoleApplication1
{
    struct Point
    {
        public int X;
        public int Y;

        public override string ToString()
        {
            return $"X = {X}, Y = {Y}";
        }

        public void Display(string name)
        {
            WriteLine(name + ": " + ToString());
        }
    }

    class Program
    {
        static void Main()
        {
            var point1 = new Point {X = 10, Y = 20};
            // it's not a reference but value type
            var point2 = point1;
            point2.X = 777;
            point2.Y = 888;
            point1.Display(nameof(point1)); // point1: X = 10, Y = 20
            point2.Display(nameof(point2)); // point2: X = 777, Y = 888

            ReadKey();
        }
    }
}

Las estructuras también pueden contener constructores, constantes, campos, métodos, propiedades, indizadores, operadores, eventos y tipos anidados, aunque si se requieren varios de estos miembros, debería considerar convertir su tipo en una clase.


Algunas sugerencias de MS sobre cuándo usar struct y cuándo usar class:

CONSIDERAR

Definir una estructura en lugar de una clase si las instancias del tipo son pequeñas y comúnmente duran poco o están comúnmente incrustadas en otros objetos.

EVITAR

definiendo una estructura a menos que el tipo tenga todas las siguientes características:

  • Lógicamente representa un solo valor, similar a los tipos primitivos (int, double, etc.)
  • Tiene un tamaño de instancia inferior a 16 bytes.
  • Es inmutable.
  • No tendrá que ser boxeado con frecuencia.

cambiar

La instrucción de switch es una instrucción de control que selecciona una sección de cambio para ejecutar desde una lista de candidatos. Una declaración de conmutación incluye una o más secciones de conmutación. Cada sección de cambio contiene una o más etiquetas de case seguidas de una o más declaraciones. Si ninguna etiqueta de caso contiene un valor coincidente, el control se transfiere a la sección default , si existe. El caso de fallos no se admite en C #, estrictamente hablando. Sin embargo, si 1 o más etiquetas de case están vacías, la ejecución seguirá el código del siguiente bloque de case que contiene código. Esto permite agrupar múltiples etiquetas de case con la misma implementación. En el siguiente ejemplo, si month es igual a 12, el código en el case 2 se ejecutará ya que las etiquetas de case 12 1 y 2 están agrupadas. Si un bloque de case no está vacío, debe haber una break antes de la siguiente etiqueta de case , de lo contrario, el compilador marcará un error.

int month = DateTime.Now.Month; // this is expected to be 1-12 for Jan-Dec

switch (month)
{
    case 12: 
    case 1: 
    case 2:
        Console.WriteLine("Winter");
        break;
    case 3: 
    case 4: 
    case 5:
        Console.WriteLine("Spring");
        break;
    case 6: 
    case 7: 
    case 8:
        Console.WriteLine("Summer");
        break;
    case 9:     
    case 10: 
    case 11:
        Console.WriteLine("Autumn");
        break;
    default:
        Console.WriteLine("Incorrect month index");
        break;
}

Un case solo se puede etiquetar con un valor conocido en el momento de la compilación (por ejemplo, 1 , "str" , Enum.A ), por lo que una variable no es una etiqueta de case válida, pero un valor const o Enum es (así como cualquier valor literal).

interfaz

Una interface contiene las firmas de métodos, propiedades y eventos. Las clases derivadas definen a los miembros ya que la interfaz solo contiene la declaración de los miembros.

Se declara una interfaz usando la palabra clave de la interface .

interface IProduct
{
    decimal Price { get; }
}

class Product : IProduct
{
    const decimal vat = 0.2M;
    
    public Product(decimal price)
    {
        _price = price;
    }
    
    private decimal _price;
    public decimal Price { get { return _price * (1 + vat); } }
}

inseguro

La palabra clave unsafe se puede usar en declaraciones de tipo o método o para declarar un bloque en línea.

El propósito de esta palabra clave es habilitar el uso del subconjunto inseguro de C # para el bloque en cuestión. El subconjunto inseguro incluye características como punteros, asignación de pila, matrices tipo C, etc.

El código inseguro no es verificable y es por eso que se desaconseja su uso. La compilación de código inseguro requiere pasar un interruptor al compilador de C #. Además, el CLR requiere que el ensamblado en ejecución tenga plena confianza.

A pesar de estas limitaciones, el código no seguro tiene usos válidos para hacer que algunas operaciones sean más eficaces (por ejemplo, indexación de matrices) o más fáciles (por ejemplo, interoperabilidad con algunas bibliotecas no administradas).

Como un ejemplo muy simple.

// compile with /unsafe
class UnsafeTest
{
   unsafe static void SquarePtrParam(int* p)
   {
      *p *= *p; // the '*' dereferences the pointer.
      //Since we passed in "the address of i", this becomes "i *= i"
   }

   unsafe static void Main()
   {
      int i = 5;
      // Unsafe method: uses address-of operator (&):
      SquarePtrParam(&i); // "&i" means "the address of i". The behavior is similar to "ref i"
      Console.WriteLine(i); // Output: 25
   }
}

Mientras trabajamos con punteros, podemos cambiar los valores de las ubicaciones de la memoria directamente, en lugar de tener que abordarlos por nombre. Tenga en cuenta que esto a menudo requiere el uso de la palabra clave fija para evitar posibles daños en la memoria, ya que el recolector de basura mueve las cosas (de lo contrario, puede obtener el error CS0212 ). Ya que una variable que se ha "arreglado" no se puede escribir, a menudo también tenemos que tener un segundo puntero que comienza apuntando a la misma ubicación que la primera.

void Main()
{
    int[] intArray = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    UnsafeSquareArray(intArray);
    foreach(int i in intArray)
        Console.WriteLine(i);
}

unsafe static void UnsafeSquareArray(int[] pArr)
{
    int len = pArr.Length;

    //in C or C++, we could say
    // int* a = &(pArr[0])
    // however, C# requires you to "fix" the variable first 
    fixed(int* fixedPointer = &(pArr[0]))
    {
        //Declare a new int pointer because "fixedPointer" cannot be written to.
        // "p" points to the same address space, but we can modify it
        int* p = fixedPointer;

        for (int i = 0; i < len; i++)
        {
            *p *= *p; //square the value, just like we did in SquarePtrParam, above
            p++;      //move the pointer to the next memory space.
                      // NOTE that the pointer will move 4 bytes since "p" is an
                      // int pointer and an int takes 4 bytes

            //the above 2 lines could be written as one, like this:
            // "*p *= *p++;"
        }
    }
}

Salida:

1
4
9
16
25
36
49
64
81
100

unsafe también permite el uso de stackalloc que asignará memoria en la pila como _alloca en la biblioteca en tiempo de ejecución de C. Podemos modificar el ejemplo anterior para usar stackalloc siguiente manera:

unsafe void Main()
{
    const int len=10;
    int* seedArray = stackalloc int[len];
    
    //We can no longer use the initializer "{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}" as before.
    // We have at least 2 options to populate the array. The end result of either
    // option will be the same (doing both will also be the same here).

    //FIRST OPTION:
    int* p = seedArray; // we don't want to lose where the array starts, so we
                        // create a shadow copy of the pointer
    for(int i=1; i<=len; i++)
        *p++ = i;
    //end of first option

    //SECOND OPTION:
    for(int i=0; i<len; i++)
        seedArray[i] = i+1;
    //end of second option

    UnsafeSquareArray(seedArray, len);
    for(int i=0; i< len; i++)
        Console.WriteLine(seedArray[i]);
}

//Now that we are dealing directly in pointers, we don't need to mess around with
// "fixed", which dramatically simplifies the code
unsafe static void UnsafeSquareArray(int* p, int len)
{
    for (int i = 0; i < len; i++)
        *p *= *p++;
}

(La salida es la misma que la anterior)

implícito

La palabra clave implicit se utiliza para sobrecargar un operador de conversión. Por ejemplo, puede declarar una clase de Fraction que debería convertirse automáticamente a double cuando sea necesario, y que puede convertirse automáticamente desde int :

class Fraction(int numerator, int denominator)
{
    public int Numerator { get; } = numerator;
    public int Denominator { get; } = denominator;
    // ...
    public static implicit operator double(Fraction f)
    {
        return f.Numerator / (double) f.Denominator;
    }
    public static implicit operator Fraction(int i)
    {
        return new Fraction(i, 1);
    }
}

verdadero Falso

Las palabras clave de true y false tienen dos usos:

  1. Como valores booleanos literales
var myTrueBool = true;
var myFalseBool = false;
  1. Como operadores que pueden sobrecargarse.
public static bool operator true(MyClass x)
{
    return x.value >= 0;
}

public static bool operator false(MyClass x)
{
    return x.value < 0;
}

La sobrecarga del operador falso fue útil antes de C # 2.0, antes de la introducción de los tipos de Nullable .
Un tipo que sobrecargue al operador true , también debe sobrecargar al operador false .

cuerda

string es un alias del tipo de datos .NET System.String , que permite almacenar texto (secuencias de caracteres).

Notación:

string a = "Hello";
var b = "world";
var f = new string(new []{ 'h', 'i', '!' }); // hi!

Cada carácter de la cadena está codificado en UTF-16, lo que significa que cada carácter requerirá un mínimo de 2 bytes de espacio de almacenamiento.

ushort

Un tipo numérico utilizado para almacenar enteros positivos de 16 bits. ushort es un alias para System.UInt16 , y ocupa 2 bytes de memoria.

El rango válido es de 0 a 65535 .

ushort a = 50; // 50
ushort b = 65536; // Error, cannot be converted
ushort c = unchecked((ushort)65536); // Overflows (wraps around to 0)

sbyte

Un tipo numérico utilizado para almacenar enteros con signo de 8 bits. sbyte es un alias para System.SByte y ocupa 1 byte de memoria. Para el equivalente sin firmar, use byte .

El rango válido es de -127 a 127 (el resto se utiliza para almacenar el letrero).

sbyte a = 127; // 127
sbyte b = -127; // -127
sbyte c = 200; // Error, cannot be converted
sbyte d = unchecked((sbyte)129); // -127 (overflows)

var

Una variable local de tipo implícito que se escribe fuertemente como si el usuario hubiera declarado el tipo. A diferencia de otras declaraciones de variables, el compilador determina el tipo de variable que esto representa en función del valor que se le asigna.

var i = 10; // implicitly typed, the compiler must determine what type of variable this is
int i = 10; // explicitly typed, the type of variable is explicitly stated to the compiler

// Note that these both represent the same type of variable (int) with the same value (10).

A diferencia de otros tipos de variables, las definiciones de variables con esta palabra clave deben inicializarse cuando se declaran. Esto se debe a que la palabra clave var representa una variable de tipo implícito.

var i;
i = 10;

// This code will not run as it is not initialized upon declaration.

La palabra clave var también se puede utilizar para crear nuevos tipos de datos sobre la marcha. Estos nuevos tipos de datos se conocen como tipos anónimos . Son muy útiles, ya que permiten a un usuario definir un conjunto de propiedades sin tener que declarar explícitamente ningún tipo de objeto primero.

Tipo anónimo

var a = new { number = 1, text = "hi" };

Consulta LINQ que devuelve un tipo anónimo

public class Dog
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public class DogWithBreed
{
    public Dog Dog { get; set; }
    public string BreedName  { get; set; }
}

public void GetDogsWithBreedNames()
{
    var db = new DogDataContext(ConnectString);
    var result = from d in db.Dogs
             join b in db.Breeds on d.BreedId equals b.BreedId
             select new 
                    {
                        DogName = d.Name,
                        BreedName = b.BreedName
                    };

    DoStuff(result);
}

Puedes usar la palabra clave var en la sentencia foreach

public bool hasItemInList(List<String> list, string stringToSearch)
{
    foreach(var item in list)
    {
        if( ( (string)item ).equals(stringToSearch) )
            return true;
    }

    return false;
}

delegar

Los delegados son tipos que representan una referencia a un método. Se utilizan para pasar métodos como argumentos a otros métodos.

Los delegados pueden mantener métodos estáticos, métodos de instancia, métodos anónimos o expresiones lambda.

class DelegateExample
{
    public void Run()
    {
        //using class method
        InvokeDelegate( WriteToConsole ); 
        
        //using anonymous method
        DelegateInvoker di = delegate ( string input ) 
        { 
            Console.WriteLine( string.Format( "di: {0} ", input ) );
            return true; 
        };
        InvokeDelegate( di ); 
        
        //using lambda expression
        InvokeDelegate( input => false ); 
    }

    public delegate bool DelegateInvoker( string input );

    public void InvokeDelegate(DelegateInvoker func)
    {
        var ret = func( "hello world" );
        Console.WriteLine( string.Format( " > delegate returned {0}", ret ) );
    }

    public bool WriteToConsole( string input )
    {
        Console.WriteLine( string.Format( "WriteToConsole: '{0}'", input ) );
        return true;
    }
}

Al asignar un método a un delegado, es importante tener en cuenta que el método debe tener el mismo tipo de retorno, así como los parámetros. Esto difiere de la sobrecarga del método 'normal', donde solo los parámetros definen la firma del método.

Los eventos se construyen sobre los delegados.

evento

Un event permite al desarrollador implementar un patrón de notificación.

Ejemplo simple

public class Server
{
    // defines the event
    public event EventHandler DataChangeEvent;

    void RaiseEvent()
    {
        var ev = DataChangeEvent;
        if(ev != null)
        {
            ev(this, EventArgs.Empty);
        }
    }
}

public class Client
{
    public void Client(Server server)
    {
        // client subscribes to the server's DataChangeEvent
        server.DataChangeEvent += server_DataChanged;
    }

    private void server_DataChanged(object sender, EventArgs args)
    {
        // notified when the server raises the DataChangeEvent
    }
}

Referencia de MSDN

parcial

La palabra clave partial se puede usar durante la definición de tipo de clase, estructura o interfaz para permitir que la definición de tipo se divida en varios archivos. Esto es útil para incorporar nuevas características en el código generado automáticamente.

File1.cs

namespace A
{
    public partial class Test
    {
        public string Var1 {get;set;}
    }
}

File2.cs

namespace A
{
    public partial class Test
    {
        public string Var2 {get;set;}
    }
}

Nota: Una clase se puede dividir en cualquier número de archivos. Sin embargo, todas las declaraciones deben estar bajo el mismo espacio de nombres y el mismo ensamblado.

Los métodos también se pueden declarar parciales usando la palabra clave partial . En este caso, un archivo contendrá solo la definición del método y otro archivo contendrá la implementación.

Un método parcial tiene su firma definida en una parte de un tipo parcial, y su implementación definida en otra parte del tipo. Los métodos parciales permiten a los diseñadores de clase proporcionar enlaces de métodos, similares a los controladores de eventos, que los desarrolladores pueden decidir implementar o no. Si el desarrollador no proporciona una implementación, el compilador elimina la firma en el momento de la compilación. Las siguientes condiciones se aplican a los métodos parciales:

  • Las firmas en ambas partes del tipo parcial deben coincidir.
  • El método debe devolver vacío.
  • No se permiten modificadores de acceso. Los métodos parciales son implícitamente privados.

- MSDN

File1.cs

namespace A
{
    public partial class Test
    {
        public string Var1 {get;set;}
        public partial Method1(string str);
    }
}

File2.cs

namespace A
{
    public partial class Test
    {
        public string Var2 {get;set;}
        public partial Method1(string str)
        {
            Console.WriteLine(str);
        }
    }
}

Nota: El tipo que contiene el método parcial también debe ser declarado parcial.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow