muhayyuddin gillani
muhayyuddin gillani

Reputation: 71

How to avoid re-memory allocation to an object

The Code below sets the value to an object using reflection. The issue is, it is reallocating the memory to the subfield (Name) of the main object (refObj). Instead of setting the value at the existing memory location.

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);
        }
    }
}

The expected output should be

This is first name
This is first name
This is second name
This is second name

but it is like

This is first name
This is first name
This is first name
This is second name

If inside the ReflectionPractice class, I move the Name object outside the "setValueToObject" method and allocate memory once then the out is correct. But in my problem scenario, I have to allocate memory to Name every time I call "setValueToObject" method. Any solution suggestions will be highly appreciated

Thanks

Upvotes: -1

Views: 637

Answers (4)

muhayyuddin gillani
muhayyuddin gillani

Reputation: 71

Setting values in this way fixed the issue. @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);
        }

Upvotes: 0

CShark
CShark

Reputation: 1563

If you're using C# 7.0 it is quite an easy fix. First to why this happens, if it isn't clear. When you fetch personName the first time, it fetches an address to the object PersonName, which is the same as the address in the refObj. Once you call setValueToObject2, the Object itself is not changed, but a new one is generated at a new address. The address is then assigned to the refObj.PersonName, but your local reference does not know any of this and still points to the initial object, which is the correct and expected behaviour.

To change this and explicitly follow changes to the source at refObj.PersonName, you'll need to declare the local variable personName as a ref-Variable. personName will no longer point to the Object itself, but to refObj.PersonName and whatever is behind that Variable, so it will also Update when you change that Variable.

A working example would look like this:

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);

Some more in-depth explanation: When you assign an Object to a Variable, you indeed assign a reference to the Object not the Value of the Object itself. This reference is a Number, wich tells us where to look for the Data of the Object. So if I say reflection.setValueToObject1(), a new Object gets generated at Address 1234 for example. This number is what the Variable refObj.PersonName will contain, you'll never see that number though. When you assign a Variable with a reference to that new object, the only thing done is to copy that number into the new Variable. So after you say personName = refObj.PersonName, personName now also holds the Address 1234 and thus points to the same Object as refObj.PersonName.

As we can see, if we set refObj.PersonName.Name = "Test" the programm will first look into refObj.PersonName and take that Address. It then goes to said Address and changes the Value of Name. The same happens if you change personName.Name. First it looks up the address in the Variable, goes to that address and changes the field Name. This is why you'll see the Name change in both Variables in this case, and this it what "reference-type" means (A value-type will create a copy, so this won't happen).

But; when you create a new Object, you also create a new Address for the Object to live at - e.g. 4567. That is what the new-keyword does: allocate some memory and create a new object there. This Address is now assigned to refObj.PersonName, but not personName (as the Assignment clearly only happens on the refObj). So personName still has the Address 1234 and therefore still points to the old Object.

What ref does: When you create the refObj, it itself (and all fields) will have a certain Address in Memory. Let's say, refObj is at 9900, and its field refObj.PersonName is at 9999. When you say personName = refObj.PersonName, the program looks what Address is stored inside refObj.PersonName (1234) and copies it to personName, but when you say ref personName = ref refObj.PersonName, it takes the Address of the field itself and copies that. So now personName has the value 9999 instead. Whenever you access personName now, it first looks at Address 9999 and then follows that to whichever Address it contains (1234 or 4567). This is why it will update as well when you change refObj.PersonName.

Or, if you're more of the visual type, this is what happens:

refObj.PersonName = new Name("Name 1")

refObj.PersonName = new Name("Name 1")

personName = refObj.PersonName

personName = refObj.PersonName

refObj.PersonName = new Name("Name 2")

refObj.PersonName = new Name("Name 2")

Compare it to what happens when you use the ref-keyword:

ref personName = ref refObj.PersonName

ref personName = ref refObj.PersonName

refObj.PersonName = new Name("Name 2")

 refObj.PersonName = new Name("Name 2")

I hope this helps to clear this up a little bit :)

Upvotes: 2

MakePeaceGreatAgain
MakePeaceGreatAgain

Reputation: 37115

The key here is this part of your code:

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);

here you assign the PersonName of the first call to the local variable personName. As you never assign anything different to personName it will allways point to the exact same instance, which is the Name-object of your first call. When calling setValueToObject2 you just create a completely new instance of the Name-class which has no relation to the first one at all. Any modification on this instance therefore is not reflected on the first instance and thus also not within personName.

Thus personName allways reflects the very first Name-instance. (reoObj as PersonData).PersonName on the other hand points to the actual (second) one.

You can easily fix that by re-assigning 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);

Upvotes: 1

Peter Csala
Peter Csala

Reputation: 22849

 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 
 */

That's why your second Console.WriteLine(personName.name); print the "this is first name".

Upvotes: 0

Related Questions