Подтвердить что ты не робот

Передача класса в качестве параметра ref в С# не всегда работает должным образом. Может ли кто-нибудь объяснить?

Я всегда думал, что параметр метода с типом класса передается по умолчанию как ссылочный параметр. По-видимому, это не всегда так. Рассмотрим эти модульные тесты в С# (используя MSTest).

[TestClass]
public class Sandbox
{
    private class TestRefClass
    {
        public int TestInt { get; set; }
    }

    private void TestDefaultMethod(TestRefClass testClass)
    {
        testClass.TestInt = 1;
    }

    private void TestAssignmentMethod(TestRefClass testClass)
    {
        testClass = new TestRefClass() { TestInt = 1 };
    }

    private void TestAssignmentRefMethod(ref TestRefClass testClass)
    {
        testClass = new TestRefClass() { TestInt = 1 };
    }

    [TestMethod]
    public void DefaultTest()
    {
        var testObj = new TestRefClass() { TestInt = 0 };
        TestDefaultMethod(testObj);
        Assert.IsTrue(testObj.TestInt == 1);
    }

    [TestMethod]
    public void AssignmentTest()
    {
        var testObj = new TestRefClass() { TestInt = 0 };
        TestAssignmentMethod(testObj);
        Assert.IsTrue(testObj.TestInt == 1);
    }

    [TestMethod]
    public void AssignmentRefTest()
    {
        var testObj = new TestRefClass() { TestInt = 0 };
        TestAssignmentRefMethod(ref testObj);
        Assert.IsTrue(testObj.TestInt == 1);
    }
}

Результаты состоят в том, что AssignmentTest() терпит неудачу, а остальные два метода тестирования пройдут. Я предполагаю, что проблема заключается в том, что назначение нового экземпляра параметру testClass приводит к разрыву ссылки на параметр, но как-то явно добавление ключевого слова ref устраняет это.

Может ли кто-нибудь дать хорошее подробное объяснение того, что происходит здесь? Я в основном просто пытаюсь расширить свои знания о С#; У меня нет конкретного сценария, который я пытаюсь решить...

4b9b3361

Ответ 1

То, что почти всегда забыто, состоит в том, что класс не передается по ссылке, ссылка на класс передается по значению.

Это важно. Вместо того, чтобы копировать весь класс (передавать по значению в стереотипном смысле), копируется ссылка на этот класс (я пытаюсь избежать "указателя" ). Это 4 или 8 байтов; гораздо более приятным, чем копирование всего класса, и по сути означает, что класс передается "по ссылке".

В этот момент метод имеет свою собственную копию ссылки на класс. Назначение -, эта ссылка ограничена внутри метода (метод переназначил только свою собственную копию ссылки).

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

Использование ключевого слова ref эффективно передает ссылку по ссылке (указатель на тип указателя).

Как всегда, Джон Скит представил очень хорошо написанный обзор:

http://www.yoda.arachsys.com/csharp/parameters.html

Обратите внимание на часть "Контрольные параметры":

Ссылочные параметры не передают значения переменных, используемых в вызов функции-члена - они используют сами переменные.

Если метод присваивает что-то ссылку ref, то копия вызывающего абонента также затрагивается (как вы заметили), потому что они смотрят на ту же ссылку на экземпляр в памяти (как против которых каждый имеет свою собственную копию).

Ответ 2

Стандартное соглашение для параметров в С# - по значению. Это верно, является ли параметр class или struct. В случае class только ссылка передается по значению, а в случае struct передается неглубокая копия всего объекта.

При вводе TestAssignmentMethod есть 2 ссылки на один объект: testObj, который живет в AssignmentTest и testClass, который живет в TestAssignmentMethod. Если вы должны были мутировать фактический объект через testClass или testObj, он был бы видимым для обеих ссылок, поскольку оба они указывают на один и тот же объект. В первой строке, хотя вы выполняете

testClass = new TestRefClass() { TestInt = 1 }

Это создает новый объект и указывает на него testClass. Это не изменяет, где ссылка testObj указывает на то, что testClass является независимой копией. В настоящее время существует 2 объекта и 2 ссылки, каждая ссылка указывает на другой экземпляр объекта.

Если вы хотите пройти по ссылочной семантике, вам нужно использовать параметр ref.

Ответ 3

В AssignmentTest используется TestAssignmentMethod, который изменяет только ссылку на объект, переданную значением.

Таким образом, сам объект передается по ссылке, но ссылка на объект передается по значению. поэтому, когда вы это сделаете:

testClass = new TestRefClass() { TestInt = 1 };

Вы изменяете локальную скопированную ссылку, переданную методу, а не ссылку, содержащуюся в тесте.

Итак, здесь:

[TestMethod]
public void AssignmentTest()
{
    var testObj = new TestRefClass() { TestInt = 0 };
    TestAssignmentMethod(testObj);
    Assert.IsTrue(testObj.TestInt == 1);
}

testObj является ссылочной переменной. Когда вы передаете его на TestAssignmentMethod(testObj);, refernce передается по значению. поэтому, когда вы меняете его в методе, исходная ссылка все еще указывает на тот же объект.

Ответ 4

Мои 2 цента

Когда класс передается методу, отправляется копия его адреса пространства памяти (направляется вам адрес дома). Так что любая операция по этому адресу с эффектом дома, но не изменит адрес. (это значение по умолчанию)

Передача класса (объекта) по ссылке имеет эффект передачи его фактического адреса вместо копии адреса. Это означает, что если вы назначаете новый объект аргументу, переданному по ссылке, он изменит фактический адрес (аналогично перемещению).: D

Вот как я это вижу.