C# Language
Palabras clave
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 @
.
-
abstract
-
as
-
base
-
bool
-
break
-
byte
-
case
-
catch
-
char
-
checked
-
class
-
const
-
continue
-
decimal
-
default
-
delegate
-
do
-
double
-
else
-
enum
-
event
-
explicit
-
extern
-
false
-
finally
-
fixed
-
float
-
for
-
foreach
-
goto
-
if
-
implicit
-
in
-
int
-
interface
-
internal
-
is
-
lock
-
long
-
namespace
-
new
-
null
-
object
-
operator
-
out
-
override
-
params
-
private
-
protected
-
public
-
readonly
-
ref
-
return
-
sbyte
-
sealed
-
short
-
sizeof
-
stackalloc
-
static
-
string
-
struct
-
switch
-
this
-
throw
-
true
-
try
-
typeof
-
uint
-
ulong
-
unchecked
-
unsafe
-
ushort
-
using
(directiva) -
using
(declaración) -
virtual
-
void
-
volatile
-
when
-
while
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() {volatileint 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
ybool
- enums tipos basados en
byte
,sbyte
,short
,ushort
,int
ouint
-
IntPtr
yUIntPtr
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, comoInterlocked.Read
yInterlocked.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);
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:
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
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
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;
}
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;
}
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
}
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;
}
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"
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,
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.");
}
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");
}
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
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
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."
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
:
utilizando declaración :
La palabra clave de
using
garantiza que los objetos que implementan la interfazIDisposable
se eliminan correctamente después del uso. Hay un tema separado para la declaración de usousando 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 deBaseClass
- Si el objeto se declara de tipo
DerivedClass
entonces seDerivedClass
el método deDerivedClass
.
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);
}
En C # 5.0, la await
no se puede usar en catch
y finally
.
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.
-
char c = 'c';
-
char c = '\u0063'; //Unicode
-
char c = '\x0063'; //Hex
-
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:
- 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.
}
- 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:
- Como valores booleanos literales
var myTrueBool = true;
var myFalseBool = false;
- 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
}
}
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.