Instrucciones de declaración
Una instrucción de declaración declara una nueva variable y, opcionalmente, la inicializa. Todas las variables tienen un tipo declarado. Puede obtener más información sobre los tipos en el artículo sobre el sistema de tipos de .NET. Normalmente, una declaración incluye un tipo y un nombre de variable. También puede incluir una inicialización: el operador =
seguido de una expresión. El tipo se puede reemplazar por var
. La declaración o la expresión pueden incluir el modificador ref
para declarar que la nueva variable hace referencia a una ubicación de almacenamiento existente.
Variables locales con tipo implícito
Las variables que se declaran en el ámbito de método pueden tener un "tipo" var
implícito. Una variable local con un tipo implícito está fuertemente tipada, igual que si usted hubiera declarado el tipo, pero es el compilador el que lo determina. Las dos declaraciones siguientes de a
y b
tienen una funcionalidad equivalente:
var a = 10; // Implicitly typed.
int b = 10; // Explicitly typed.
Importante
Cuando var
se usa con tipos de referencia que aceptan valores NULL, siempre implica un tipo de referencia que acepta valores NULL, aunque el tipo de expresión no los acepte. El análisis de null-state del compilador protege frente a la desreferenciación de un posible valor null
. Si la variable nunca se asigna a una expresión que pueda ser NULL, el compilador no emitirá ninguna advertencia. Si asigna la variable a una expresión que podría ser NULL, debe probar que no sea NULL antes de desreferenciarla para evitar advertencias.
Un uso común de la palabra clave var
es con las expresiones de invocación del constructor. El uso de var
permite no repetir un nombre de tipo en una declaración de variable y una creación de instancias de objeto, como se muestra en el ejemplo siguiente:
var xs = new List<int>();
A partir de C# 9.0, se puede usar una expresión new
de con tipo de destino como alternativa:
List<int> xs = new();
List<int>? ys = new();
En la coincidencia de patrones, la palabra clave var
se usa en un patrón var
.
En el siguiente ejemplo se muestran dos expresiones de consulta. En la primera expresión, se permite el uso de var
pero no es necesario, ya que el tipo del resultado de la consulta se puede indicar explícitamente como IEnumerable<string>
. En cambio, en la segunda expresión, var
permite que el resultado sea una colección de tipos anónimos y solo el compilador puede tener acceso al nombre de ese tipo. El uso de var
elimina la necesidad de crear una nueva clase para el resultado. En el segundo ejemplo, la variable item
de la iteración foreach
también debe tener un tipo implícito.
// Example #1: var is optional when
// the select clause specifies a string
string[] words = { "apple", "strawberry", "grape", "peach", "banana" };
var wordQuery = from word in words
where word[0] == 'g'
select word;
// Because each element in the sequence is a string,
// not an anonymous type, var is optional here also.
foreach (string s in wordQuery)
{
Console.WriteLine(s);
}
// Example #2: var is required because
// the select clause specifies an anonymous type
var custQuery = from cust in customers
where cust.City == "Phoenix"
select new { cust.Name, cust.Phone };
// var must be used because each item
// in the sequence is an anonymous type
foreach (var item in custQuery)
{
Console.WriteLine("Name={0}, Phone={1}", item.Name, item.Phone);
}
Variables locales de tipo ref
Agregue la palabra clave ref
antes del tipo de una variable para declarar una variable local ref
. Una variable local ref
es una variable que hace referencia a otro almacenamiento. Suponga que el método GetContactInformation
se declara como un valor devuelto por referencia:
public ref Person GetContactInformation(string fname, string lname)
Vamos a contrastar estas dos asignaciones:
Person p = contacts.GetContactInformation("Brandie", "Best");
ref Person p2 = ref contacts.GetContactInformation("Brandie", "Best");
La variable p
contiene una copia del valor devuelto de GetContactInformation
. Es una ubicación de almacenamiento independiente de la variante ref
devuelta desde GetContactInformation
. Si cambia cualquier propiedad de p
, cambiará una copia de Person
.
La variable p2
hace referencia a la ubicación de almacenamiento para la variable ref
devuelta desde GetContactInformation
. Es el mismo almacenamiento que la variante ref
devuelta desde GetContactInformation
. Si cambia cualquier propiedad de p2
, cambiará esa instancia única de Person
.
Puede acceder a un valor por referencia de la misma manera. En algunos casos, acceder a un valor por referencia aumenta el rendimiento, ya que evita una operación de copia potencialmente cara. Por ejemplo, en la instrucción siguiente se muestra cómo es posible definir un valor local de referencia que se usa para hacer referencia a un valor.
ref VeryLargeStruct reflocal = ref veryLargeStruct;
La palabra clave ref
se usa antes de la declaración de variable local y antes del valor en el segundo ejemplo. Si no se incluyen ambas palabras clave ref
en la asignación y declaración de la variable en ambos ejemplos, se produce el error del compilador CS8172, "No se puede inicializar una variable por referencia con un valor".
ref VeryLargeStruct reflocal = ref veryLargeStruct; // initialization
refLocal = ref anotherVeryLargeStruct; // reassigned, refLocal refers to different storage.
Las variables locales ref todavía deben inicializarse cuando se declaran.
En el ejemplo siguiente, se define una clase NumberStore
que almacena una matriz de valores enteros. El método FindNumber
devuelve por referencia el primer número que es mayor o igual que el número que se pasa como argumento. Si ningún número es mayor o igual que el argumento, el método devuelve el número en el índice 0.
using System;
class NumberStore
{
int[] numbers = { 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023 };
public ref int FindNumber(int target)
{
for (int ctr = 0; ctr < numbers.Length; ctr++)
{
if (numbers[ctr] >= target)
return ref numbers[ctr];
}
return ref numbers[0];
}
public override string ToString() => string.Join(" ", numbers);
}
En el ejemplo siguiente, se llama al método NumberStore.FindNumber
para recuperar el primer valor que es mayor o igual que 16. Después, el autor de la llamada duplica el valor devuelto por el método. En el resultado del ejemplo se muestra el cambio reflejado en el valor de los elementos de matriz de la instancia NumberStore
.
var store = new NumberStore();
Console.WriteLine($"Original sequence: {store.ToString()}");
int number = 16;
ref var value = ref store.FindNumber(number);
value *= 2;
Console.WriteLine($"New sequence: {store.ToString()}");
// The example displays the following output:
// Original sequence: 1 3 7 15 31 63 127 255 511 1023
// New sequence: 1 3 7 15 62 63 127 255 511 1023
Sin que se admitan los valores devueltos de referencia, este tipo de operación se realiza al devolver el índice del elemento de matriz junto con su valor. Después, el autor de la llamada puede usar este índice para modificar el valor en una llamada al método independiente. En cambio, el autor de la llamada también puede modificar el índice para tener acceso a otros valores de matriz y, posiblemente, modificarlos.
El siguiente ejemplo muestra cómo se podría reescribir el método FindNumber
para usar la reasignación de variable local ref:
using System;
class NumberStore
{
int[] numbers = { 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023 };
public ref int FindNumber(int target)
{
ref int returnVal = ref numbers[0];
var ctr = numbers.Length - 1;
while ((ctr >= 0) && (numbers[ctr] >= target))
{
returnVal = ref numbers[ctr];
ctr--;
}
return ref returnVal;
}
public override string ToString() => string.Join(" ", numbers);
}
Esta segunda versión es más eficaz con secuencias más largas en escenarios donde el número buscado está más cerca del final de la matriz, ya que en la matriz se itera desde el final hacia el principio, lo que hace que se examinen menos elementos.
El compilador aplica reglas de ámbito en variables ref
: variables locales ref
, parámetros ref
y campos ref
en tipos ref struct
. Las reglas garantizan que una referencia no sobreviva al objeto al que hace referencia. Consulte la sección sobre las reglas de ámbito del artículo sobre los parámetros de método.
ref y readonly
El modificador readonly
se puede aplicar a variables locales ref
y campos ref
. El modificador readonly
afecta a la expresión de la derecha. Vea las siguientes declaraciones de ejemplo:
ref readonly int aConstant; // aConstant can't be value-reassigned.
readonly ref int Storage; // Storage can't be ref-reassigned.
readonly ref readonly int CantChange; // CantChange can't be value-reassigned or ref-reassigned.
- Reasignación de valor significa que se reasigna el valor de la variable.
- Asignación de referencia significa que la variable ahora hace referencia a un objeto diferente.
Las declaraciones readonly ref
y readonly ref readonly
solo son válidas en campos ref
de ref struct
.
Referencia con ámbito
La palabra clave contextual scoped
restringe la duración de un valor. El modificador scoped
restringe la duración ref-safe-to-escape o safe-to-escape, respectivamente, al método actual. De hecho, al agregar el modificador scoped
se afirma que el código no extenderá la duración de la variable.
Puede aplicar scoped
a un parámetro o variable local. El modificador scoped
se puede aplicar a parámetros y variables locales cuando el tipo es ref struct
. De lo contrario, el modificador scoped
solo se puede aplicar a las variables locales que son de tipos ref. Esto incluye las variables locales declaradas con el modificador ref
y los parámetros declarados con los modificadores in
, ref
o out
.
El modificador scoped
se agrega implícitamente a this
en los métodos declarados en struct
y en parámetros out
y ref
cuando el tipo es ref struct
.