Передача класса в качестве параметра ref в C # не всегда работает должным образом.Кто-нибудь может объяснить? - PullRequest
25 голосов
/ 03 апреля 2012

Я всегда думал, что параметр метода с типом класса по умолчанию передается в качестве ссылочного параметра.Видимо, это не всегда так.Рассмотрим эти модульные тесты в C # (с использованием 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 исправляет это.

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

Ответы [ 4 ]

32 голосов
/ 03 апреля 2012

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

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

На данный момент метод имеет собственную копию ссылки на класс . Присвоение этой ссылки в рамках метода (метод переназначает только свою собственную копию ссылки).

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

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

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

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

Обратите внимание на часть «Справочные параметры»:

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

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

7 голосов
/ 03 апреля 2012

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

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

testClass = new TestRefClass() { TestInt = 1 }

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

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

2 голосов
/ 03 апреля 2012

Мои 2 цента

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

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

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

2 голосов
/ 03 апреля 2012

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);, ссылка передается по значению. поэтому, когда вы меняете его в методе, исходная ссылка все еще указывает на тот же объект .

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