c# - referencias - Igualdad de referencia de los tipos de valor
posible comparacion de referencias no intencionada c# (4)
He hecho algunas pruebas de palabras clave de ref
y hay uno que no puedo entender:
static void Test(ref int a, ref int b)
{
Console.WriteLine(Int32.ReferenceEquals(a,b));
}
static void Main(string[] args)
{
int a = 4;
Test(ref a, ref a);
Console.ReadLine();
}
¿Por qué este código muestra False
? Sé que int
es un tipo de valor, pero aquí debería pasar referencias al mismo objeto.
Sé que int es un tipo de valor, pero aquí debería pasar referencias al mismo objeto.
Sí, la referencia que se pasa al método es la misma, pero están encuadradas (convertidas a tipo de objeto / referencia) en el método ReferenceEquals
.
Es por eso que el resultado de su prueba devuelve falso, ya que está comparando referencias de dos objetos diferentes, debido al boxeo .
Al comparar tipos de valores. Si
objA
yobjB
son tipos de valor, estánobjB
objA
objB
al métodoReferenceEquals
. Esto significa que si tantoobjA
comoobjB
representan la misma instancia de un tipo de valor, el métodoReferenceEquals
, sin embargo, devuelve false
¿Por qué este código muestra
False
?
Porque int a
e int b
están siendo object.ReferenceEquals
cuando llamas a object.ReferenceEquals
. Cada entero está encuadrado dentro de una instancia de object
. Por lo tanto, en realidad estás comparando referencias entre dos valores en caja, que claramente no son iguales.
Puede ver esto fácilmente si observa el CIL generado para el método:
Test:
IL_0000: nop
IL_0001: ldarg.0 Load argument a
IL_0002: ldind.i4
IL_0003: box System.Int32
IL_0008: ldarg.1 Load argument b
IL_0009: ldind.i4
IL_000A: box System.Int32
IL_000F: call System.Object.ReferenceEquals
IL_0014: call System.Console.WriteLine
IL_0019: nop
IL_001A: ret
La verificación de la igualdad de ubicación de almacenamiento se puede lograr mediante el uso de CIL verificable (como en la respuesta de @leppie ) o mediante unsafe
código unsafe
:
unsafe static void Main(string[] args)
{
int a = 4;
int b = 5;
Console.WriteLine(Test(ref a, ref a)); // True
Console.WriteLine(Test(ref a, ref b)); // False;
}
unsafe static bool Test(ref int a, ref int b)
{
fixed (int* refA = &a)
fixed (int* refB = &b)
{
return refA == refB;
}
}
Esto no se puede hacer directamente en C #.
Sin embargo puedes implementarlo en CIL verificable:
.method public hidebysig static bool Test<T>(!!T& a, !!T& b) cil managed
{
.maxstack 8
ldarg.0
ldarg.1
ceq
ret
}
Pruebas
int a = 4, b = 4, c = 5;
int* aa = &a; // unsafe needed for this
object o = a, p = o;
Console.WriteLine(Test(ref a, ref a)); // True
Console.WriteLine(Test(ref o, ref o)); // True
Console.WriteLine(Test(ref o, ref p)); // False
Console.WriteLine(Test(ref a, ref b)); // False
Console.WriteLine(Test(ref a, ref c)); // False
Console.WriteLine(Test(ref a, ref *aa)); // True
// all of the above works for fields, parameters and locals
Notas
En realidad, esto no busca la misma referencia, pero es aún más minucioso, ya que se asegura de que ambas sean la misma ''ubicación'' (o que estén referenciadas en la misma variable). Esto es mientras que la tercera línea devuelve false
aunque o == p
devuelve true
. Sin embargo, la utilidad de esta prueba de ''ubicación'' es muy limitada.
La confusión aquí se debe a que, a diferencia de los punteros (como en *), "ref" en C # no es una parte de un tipo, sino una parte de una firma de método. Se aplica al parámetro y significa "no debe copiarse". No significa "este argumento tiene tipo de referencia".
El parámetro pasado por ref, en lugar de representar una nueva ubicación de almacenamiento, es en cambio un alias para alguna ubicación existente. Cómo se crea el alias es técnicamente un detalle de implementación. La mayoría de los alias se implementan como referencias gestionadas, pero no siempre. En algunos casos relacionados con async, por ejemplo, una referencia a un elemento de matriz podría representarse internamente como una combinación de matriz e índice.
Esencialmente para todos los propósitos, a y b aún son entendidos por C # como variables int-tipeadas. Es legal y completamente normal usarlos en cualquier expresión que tome valores int como a + b, o SomeMethod (a, b) y en esos casos se usan los valores int reales almacenados en ayb.
Realmente no hay concepto de una "referencia" como una entidad con la que pueda trabajar directamente en C #. A diferencia de los punteros, debe asumirse que los valores reales de las referencias administradas pueden cambiar en cualquier momento, o incluso de forma asíncrona, por GC, por lo que el conjunto de escenarios significativos en las referencias administradas sería extremadamente limitado.