El siguiente código establece el valor de un objeto usando la reflexión. El problema es que está reasignando la memoria al subcampo (Nombre) del objeto principal (refObj). En lugar de establecer el valor en la ubicación de memoria existente.
namespace MemoryAllocation
{
class Name
{
public string name;
public Name(string n)
{
name = n;
}
}
class PersonData
{
public Name PersonName;
public int age;
public PersonData()
{
age = 0;
PersonName = new Name("");
}
}
class ReflectionPractice
{
object localObj;
Type type;
public ReflectionPractice( object refObj)
{
localObj = refObj;
type = refObj.GetType();
}
public void setValueToObject1()
{
FieldInfo fi1 = type.GetField("age");
FieldInfo fi2 = type.GetField("PersonName");
Name personName = new Name("This is first name");
fi1.SetValue(localObj, 34);
fi2.SetValue(localObj, personName);
}
public void setValueToObject2()
{
FieldInfo fi1 = type.GetField("age");
FieldInfo fi2 = type.GetField("PersonName");
Name personName = new Name("This is second name");
fi1.SetValue(localObj, 27);
fi2.SetValue(localObj, personName);
}
}
class Program
{
static void Main(string[] args)
{
object refObj = new PersonData();
Name personName;
ReflectionPractice reflection = new ReflectionPractice(refObj);
reflection.setValueToObject1();
personName = (refObj as PersonData).PersonName;
Console.WriteLine(personName.name);
Console.WriteLine((refObj as PersonData).PersonName.name);
reflection.setValueToObject2();
Console.WriteLine(personName.name);
Console.WriteLine((refObj as PersonData).PersonName.name);
}
}
}
El resultado esperado debe ser
This is first name
This is first name
This is second name
This is second name
Pero es como
This is first name
This is first name
This is first name
This is second name
Si dentro de la clase ReflectionPractice, muevo el objeto Name fuera del método "setValueToObject" y asigno memoria una vez, entonces la salida es correcta. Pero en mi situación problemática, tengo que asignar memoria a Nombre cada vez que llamo al método "setValueToObject". Cualquier sugerencia de solución será muy apreciada.
Gracias
4 respuestas
Si está utilizando C # 7.0, es una solución bastante fácil. Primero por qué sucede esto, si no está claro. Cuando obtiene personName
la primera vez, obtiene una dirección para el objeto PersonName
, que es la misma que la dirección en refObj
. Una vez que llame a setValueToObject2
, el Objeto en sí no cambia, pero se genera uno nuevo en una nueva dirección. Luego, la dirección se asigna a refObj.PersonName
, pero su referencia local no sabe nada de esto y aún apunta al objeto inicial, que es el comportamiento correcto y esperado.
Para cambiar esto y seguir explícitamente los cambios en la fuente en refObj.PersonName
, deberá declarar la variable local personName
como ref
- Variable. personName
ya no apuntará al Objeto en sí, sino a refObj.PersonName
y lo que esté detrás de esa Variable, por lo que también se Actualizará cuando cambie esa Variable.
Un ejemplo de trabajo se vería así:
object refObj = new PersonData();
ReflectionPractice reflection = new ReflectionPractice(refObj);
reflection.setValueToObject1();
ref Name personName = ref (refObj as PersonData).PersonName;
Console.WriteLine(personName.name);
Console.WriteLine((refObj as PersonData).PersonName.name);
reflection.setValueToObject2();
Console.WriteLine(personName.name);
Console.WriteLine((refObj as PersonData).PersonName.name);
Una explicación más detallada: Cuando asigna un Objeto a una Variable, de hecho asigna una referencia al Objeto, no el Valor del Objeto en sí. Esta referencia es un número, que nos dice dónde buscar los datos del objeto. Entonces, si digo reflection.setValueToObject1()
, se genera un nuevo Objeto en la Dirección 1234
, por ejemplo. Este número es lo que contendrá la Variable refObj.PersonName
, aunque nunca verás ese número. Cuando asigna una variable con una referencia a ese nuevo objeto, lo único que se hace es copiar ese número en la nueva variable. Entonces, después de decir personName = refObj.PersonName
, personName
ahora también contiene la Dirección 1234
y, por lo tanto, apunta al mismo Objeto que refObj.PersonName
.
Como podemos ver, si configuramos refObj.PersonName.Name = "Test"
, el programa primero buscará en refObj.PersonName
y tomará esa dirección. Luego va a dicha dirección y cambia el valor de Name
. Lo mismo sucede si cambia personName.Name
. Primero busca la dirección en la variable, va a esa dirección y cambia el nombre del campo. Es por eso que verá el cambio de Nombre en ambas Variables en este caso, y esto es lo que significa "tipo de referencia" (Un tipo de valor creará una copia, por lo que esto no sucederá).
Pero; cuando creas un nuevo Objeto, también creas una nueva Dirección para que el Objeto viva, p. 4567
. Eso es lo que hace la new
palabra clave: asignar algo de memoria y crear un nuevo objeto allí. Esta dirección ahora está asignada a refObj.PersonName
, pero no personName
(ya que la asignación claramente solo ocurre en el refObj). Entonces personName
todavía tiene la Dirección 1234
y, por lo tanto, todavía apunta al Objeto anterior.
Lo que hace ref
: cuando crea el refObj
, él mismo (y todos los campos) tendrán una determinada Dirección en la memoria. Digamos que refObj
está en 9900
, y su campo refObj.PersonName
está en 9999
. Cuando dice personName = refObj.PersonName
, el programa mira qué Dirección está almacenada dentro de refObj.PersonName
(1234) y la copia en personName
, pero cuando dice ref personName = ref refObj.PersonName
, toma la Dirección de el campo en sí y copia eso. Entonces ahora personName
tiene el valor 9999
en su lugar. Cada vez que accede a personName
ahora, primero mira la Dirección 9999
y luego la sigue a la Dirección que contenga (1234 o 4567). Es por eso que se actualizará también cuando cambie refObj.PersonName
.
O, si eres más del tipo visual, esto es lo que sucede:
refObj.PersonName = new Name("Name 1")
personName = refObj.PersonName
refObj.PersonName = new Name("Name 2")
Compárelo con lo que sucede cuando usa la palabra clave ref
:
ref personName = ref refObj.PersonName
refObj.PersonName = new Name("Name 2")
Espero que esto ayude a aclarar esto un poco :)
var refObj = new PersonData();
/* You have only a single Name object
* There is a single reference to it
*/
var reflection = new ReflectionPractice(refObj);
reflection.setValueToObject1();
/* You have two Name objects
* Where the name is "" there is no reference anymore
* Where the name is "This is first name" there is a single reference
*/
var personName = (refObj as PersonData).PersonName;
/* You have two Name objects
* Where the name is "" there is no reference anymore
* Where the name is "This is first name" there are two references
*/
reflection.setValueToObject2();
/* You have three Name objects
* Where the name is "" there is no reference anymore
* Where the name is "This is first name" there is one reference (personName variable)
* Where the name is "This is second name" there is one reference
*/
Es por eso que su segundo Console.WriteLine(personName.name);
imprime "este es su nombre".
Establecer valores de esta manera solucionó el problema. @CShark
public void setValueToObject1()
{
FieldInfo fi1 = type.GetField("age");
FieldInfo fi2 = type.GetField("PersonName");
fi1.SetValue(localObj, 34);
TypedReference reference = __makeref(localObj);
object obj = fi2.GetValueDirect(reference);
Type localtype = obj.GetType();
FieldInfo filocal = localtype.GetField("name");
object o = "first name";
filocal.SetValueDirect(__makeref(obj),o);
fi2.SetValue(localObj, obj);
}
public void setValueToObject2()
{
FieldInfo fi1 = type.GetField("age");
FieldInfo fi2 = type.GetField("PersonName");
TypedReference reference = __makeref(localObj);
object obj = fi2.GetValueDirect(reference);
Type localtype = obj.GetType();
FieldInfo filocal = localtype.GetField("name");
object o = "second name";
filocal.SetValueDirect(__makeref(obj), o);
fi2.SetValue(localObj, obj);
}
La clave aquí es esta parte de su código:
personName = (refObj as PersonData).PersonName;
Console.WriteLine(personName.name);
Console.WriteLine((refObj as PersonData).PersonName.name);
reflection.setValueToObject2();
Console.WriteLine(personName.name);
Console.WriteLine((refObj as PersonData).PersonName.name);
Aquí asigna el PersonName
de la primera llamada a la variable local personName
. Como nunca asigna nada diferente a personName
, siempre señalará exactamente la misma instancia, que es el Name
- objeto de su primera llamada. Al llamar a setValueToObject2
, simplemente crea una instancia completamente nueva de la clase Name
que no tiene ninguna relación con la primera. Cualquier modificación en esta instancia, por lo tanto, no se refleja en la primera instancia y, por lo tanto, tampoco dentro de personName
.
Por lo tanto, personName
siempre refleja la primera Name
instancia. (reoObj as PersonData).PersonName
por otro lado apunta al real (segundo).
Puede solucionarlo fácilmente reasignando personName
:
personName = (refObj as PersonData).PersonName;
Console.WriteLine(personName.name);
Console.WriteLine((refObj as PersonData).PersonName.name);
reflection.setValueToObject2();
personName = (refObj as PersonData).PersonName; // get the new instance
Console.WriteLine(personName.name);
Console.WriteLine((refObj as PersonData).PersonName.name);
Preguntas relacionadas
Nuevas preguntas
c#
C # (pronunciado "see sharp") es un lenguaje de programación multi-paradigma de alto nivel, estáticamente tipado desarrollado por Microsoft. El código C # generalmente se dirige a la familia de herramientas y tiempos de ejecución .NET de Microsoft, que incluyen .NET Framework, .NET Core y Xamarin, entre otros. Use esta etiqueta para preguntas sobre el código escrito en las especificaciones formales de C # o C #.