Замена объекта на так, чтобы все ссылки на него сохранялись

Я хотел больше узнать о том, как работает ключевое слово ref, поэтому провел следующий эксперимент:

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        var main = new Main { Property = 1 };
        var dependent = new Dependent(main);

        void ChangeRef(ref Main Oldmain, Main newMain)
        {
            Oldmain = newMain;

        }

        ChangeRef(ref main, new Main { Property = 5 });

        Assert.AreEqual(5,dependent.Main.Property);
    }
}

public class Main
{
    public int Property { get; set; }
}

public class Dependent
{
    public Dependent(Main main)
    {
        Main = main;
    }

    public Main Main { get; set; }
}

Как вы можете видеть, я ожидал, что смогу заменить объект, на который ссылается main, сохраняя при этом ссылку, но тест не проходит и значение по-прежнему равно 1. Может ли кто-нибудь уточнить, почему это не работает, или указать мне место где я могу прочитать больше?

Обновление: как кто-то ответил ниже, но позже удалил. Почему не работает, если я передаю основной объект по ссылке на зависимый в конструкторе? Разве они не должны иметь одинаковую ссылку?


person Erik83    schedule 19.02.2018    source источник
comment
Вы создали Main. Вы создали Dependent со ссылкой на этот Main. Затем вы создаете совершенно новый файл Main. dependent не видит этого Main. dependent не имеет ссылки на переменную main — она содержит ссылку на объект, на который указывает main переменная в этот момент в время. Если указать main переменную на другой объект (как вы сделали здесь), dependent волшебным образом ничего об этом не узнает. Теперь, если бы ChangeRef не назначал Oldmain, а внутри менял свои свойства, то все работало бы так, как вы ожидаете.   -  person mjwills    schedule 19.02.2018
comment
Потому что все являются ссылочными типами. Вы создаете main так, чтобы он указывал на ячейку памяти №1, а затем вы создавали depend.Main, указывающую на ту же ячейку памяти №1. Затем вы создали новый объект Main {Property = 5}, указывающий на ячейку памяти №2, а затем назначив main указывающей на №2. Но depend.Main по-прежнему указывает на №1.   -  person Johnny    schedule 19.02.2018
comment
Хорошо, но есть ли способ заменить объект, на который указывают зависимые объекты, не меняя только все их свойства?   -  person Erik83    schedule 19.02.2018
comment
@mjwills Нет, это не сработает. Я имею в виду кое-что другое, потому что я пытаюсь объяснить, указывая на память, это может сбить с толку... Спасибо за указание.   -  person Johnny    schedule 19.02.2018


Ответы (2)


Как указывали другие, вы не можете мгновенно заставить все переменные и поля в вашей программе указывать на другой экземпляр.

Но если вы хотите отразить изменение во всех частях программы, самый простой способ — обернуть его в другой класс (например, ваш класс Dependent). Затем вы можете поделиться классом с другими частями программы и вместо этого изменить его свойства:

class SomeOtherObject
{
    readonly Dependent _dependent;
    public Dependent { get { return _dependent; }}

    public SomeOtherObject(Dependent dependent)
    {
        _dependent = dependent;
    }

    public void Print()
    {
        Console.WriteLine(_dependent.Main.Property);
    }
}

Итак, теперь вы можете сделать это:

var dependent = new Dependent(new Main { Property = 1 });
var someOtherObject = new SomeOtherObject(dependent);

// this will print "1"
someOtherObject.Print();

dependent.Main = new Main { Property = 5; };

// this will print "5"
someOtherObject.Print();

В этом случае, очевидно, простое изменение dependent.Main.Property тоже поможет. Таким образом, если все части вашей программы указывают на один и тот же объект, вы можете изменить его (то есть изменить его внутренние данные), и все увидят изменение, но вы не можете заставить все части вашей программы изменить то, на что они указывают.

Стоит отметить, что вы должны быть осторожны, делая это в многопоточных программах; вы редко хотите, чтобы какой-то другой поток мог случайным образом изменить ваши внутренние данные.

По этой же причине лучше стараться, чтобы ваши свойства были доступны только для чтения, а объекты, если это возможно, неизменяемы.

person Groo    schedule 19.02.2018

После этого

var main = new Main { Property = 1 };

У вас есть объект типа Main, выделенный где-то в памяти (назовем его Main1), по некоторому адресу памяти X, и переменная main указывает на этот объект. «Точки» означают, что он буквально хранит адрес этого объекта Main1, поэтому main содержит X.

Затем вы передаете ссылку на Main1 конструктору объекта Dependent

var dependent = new Dependent(main);

Объект Dependent также размещен где-то в памяти, и в одном из его полей хранится ссылка на объект Main1. Таким образом, dependent.Main также хранит X.

Когда вы делаете

ChangeRef(ref main, new Main { Property = 5 });

Вы размещаете новый объект Main5 где-то по адресу памяти Y. Теперь вы изменяете адресную переменную, на которую указывает main. Раньше он хранил адрес X (адрес Main1), теперь он хранит адрес Y (адрес Main5). Но dependent.Main по-прежнему хранит адрес X, потому что вы его никак не меняли, поэтому он по-прежнему указывает на объект Main1.

person Evk    schedule 19.02.2018
comment
Хорошо, но я хочу заменить main1 не ссылкой, а фактическим объектом, чтобы все ссылки, указывающие на этот объект, указывали на мой новый объект. Это возможно? - person Erik83; 19.02.2018
comment
@ Erik83 нет, это вообще невозможно. - person Evk; 19.02.2018
comment
Ok. Приятно знать, что я могу перестать гоняться за этим... =) Но не кажется ли это странным? Или для этого есть соображения безопасности? - person Erik83; 19.02.2018
comment
@ Erik83 ну, мне это не кажется странным, на практике мне никогда не приходилось этого делать. В вашем примере вы можете просто установить main.Property = 5, и это изменение будет отражено для всех, кто использует этот объект. Вам не нужно ref для этого. - person Evk; 19.02.2018
comment
хорошо, если у вас есть сложный объект с большим количеством вложенных объектов или если вы просто не можете изменить свойства. Я просто подумал, что это странно, что невозможно записать прямо в это место в памяти... - person Erik83; 19.02.2018
comment
@ Erik83 хорошо, что это управляемый язык, а не C. Он просто не предназначен для прямой работы с памятью таким образом. Например, сборщик мусора может свободно перемещать объекты в памяти по своему усмотрению, и он может делать это прямо в процессе перезаписи памяти объектов. - person Evk; 19.02.2018