Как избежать повторного выделения памяти для объекта - PullRequest
0 голосов
/ 28 апреля 2020

Код ниже устанавливает значение объекта, используя отражение. Проблема в том, что она перераспределяет память в подполе (Name) основного объекта (refObj). Вместо установки значения в существующую ячейку памяти.

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

Ожидаемый результат должен быть

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

, но это похоже на

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

Если внутри Класс ReflectionPractice, я перемещаю объект Name за пределы метода "setValueToObject" и выделяю память один раз, тогда выход будет правильным. Но в моем проблемном сценарии мне приходится выделять память для Name каждый раз, когда я вызываю метод setValueToObject. Любые предложения решения будут высоко оценены

Спасибо

Ответы [ 4 ]

2 голосов
/ 28 апреля 2020

Если вы используете C# 7.0, это довольно легко исправить. Сначала о том, почему это происходит, если неясно. Когда вы выбираете personName в первый раз, он выбирает адрес для объекта PersonName, который совпадает с адресом в refObj. Как только вы вызываете setValueToObject2, сам объект не изменяется, но новый генерируется по новому адресу. Затем адрес присваивается refObj.PersonName, но ваша локальная ссылка ничего не знает об этом и все еще указывает на исходный объект, который является правильным и ожидаемым поведением.

Чтобы изменить это и явно следовать изменениям к источнику на refObj.PersonName вам нужно будет объявить локальную переменную personName как ref -Вариабельную. personName больше не будет указывать на сам объект, но на refObj.PersonName и все, что находится за этой переменной, поэтому он также будет обновляться при изменении этой переменной.

Рабочий пример будет выглядеть следующим образом:

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

Более подробное объяснение: Когда вы присваиваете объект переменной, вы действительно назначаете ссылку на объект, а не значение самого объекта. Эта ссылка является номером, который говорит нам, где искать данные объекта. Поэтому, если я скажу reflection.setValueToObject1(), новый объект будет сгенерирован, например, по адресу 1234. Это число - то, что будет содержать переменная refObj.PersonName, хотя вы никогда не увидите это число. Когда вы назначаете переменную со ссылкой на этот новый объект, единственное, что нужно сделать, это скопировать это число в новую переменную. Поэтому после того, как вы скажете personName = refObj.PersonName, personName теперь также содержит адрес 1234 и, таким образом, указывает на тот же объект, что и refObj.PersonName.

. Как мы видим, если мы установим refObj.PersonName.Name = "Test" программу сначала заглянем в refObj.PersonName и возьмем этот адрес. Затем он переходит на указанный адрес и изменяет значение Name. То же самое происходит, если вы измените personName.Name. Сначала он ищет адрес в переменной, переходит на этот адрес и меняет поле Name. Вот почему в этом случае вы увидите изменение имени в обеих переменных, и именно это означает «ссылочный тип» (тип значения создаст копию, поэтому этого не произойдет).

Но; когда вы создаете новый объект, вы также создаете новый адрес для объекта, чтобы жить на - например, 4567. Вот что делает ключевое слово new: выделяет немного памяти и создает там новый объект. Этот адрес теперь присваивается refObj.PersonName, но , а не personName (поскольку назначение явно происходит только в refObj). Таким образом, personName все еще имеет адрес 1234 и, следовательно, все еще указывает на старый объект.

Что делает ref: Когда вы создаете refObj, он сам (и все поля) будет иметь определенный адрес в памяти. Допустим, refObj находится на 9900, а его поле refObj.PersonName на 9999. Когда вы говорите personName = refObj.PersonName, программа смотрит, какой адрес хранится внутри refObj.PersonName (1234), и копирует его в personName, но когда вы говорите ref personName = ref refObj.PersonName, она берет адрес самого поля и копирует его. Так что теперь personName имеет значение 9999. Всякий раз, когда вы обращаетесь к personName сейчас, он сначала просматривает адрес 9999, а затем следует за тем адресом, который он содержит (1234 или 4567). Вот почему он также будет обновляться при изменении refObj.PersonName.

Или, если вы больше визуального типа, вот что происходит:

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

refObj.PersonName = new Name(

personName = refObj.PersonName

personName = refObj.PersonName

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

refObj.PersonName = new Name(

Сравнить что происходит, когда вы используете ключевое слово ref:

ref personName = ref refObj.PersonName

ref personName = ref refObj.PersonName

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

refObj.PersonName = new Name(

Надеюсь, это поможет немного прояснить ситуацию:)

1 голос
/ 28 апреля 2020

Ключом здесь является эта часть вашего кода:

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

здесь вы присваиваете PersonName первого вызова локальной переменной personName. Поскольку вы никогда не назначаете что-либо другое для personName, оно всегда будет указывать на точно такой же экземпляр, который является Name -объектом вашего первого вызова. При вызове setValueToObject2 вы просто создаете совершенно новый экземпляр класса Name, который вообще не имеет отношения к первому. Поэтому любая модификация в этом случае не отражается в первом случае и, следовательно, также не находится в пределах personName.

Таким образом, personName всегда отражает самый первый Name -экземпляр. (reoObj as PersonData).PersonName, с другой стороны, указывает на фактическую (вторую).

Вы можете легко исправить это, переназначив 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);
0 голосов
/ 28 апреля 2020

Установка значений таким способом устранила проблему. @ 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);
        }
0 голосов
/ 28 апреля 2020
 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 
 */

Вот почему ваш второй Console.WriteLine(personName.name); печатает "это имя".

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...