Передача .NET параметров - по ссылке v / s по значению - PullRequest
17 голосов
/ 24 января 2010

Я пытаюсь подтвердить свое понимание того, как C # /. NET / CLR обрабатывает типы значений и ссылочные типы. Я прочитал очень много противоречивых объяснений, я все еще

Это то, что я понимаю сегодня, пожалуйста, поправьте меня, если мои предположения неверны.

Типы значений, такие как int и т. Д., Живут в стеке, ссылочные типы живут в управляемой куче однако если ссылочный тип имеет, например, переменную экземпляра типа double, он будет жить вместе со своим объектом в кучу

Вторая часть меня больше всего смущает.

Давайте рассмотрим простой класс с именем Person.

У человека есть свойство с именем Name.

Допустим, я создаю экземпляр Person в другом классе, назовем его UselessUtilityClass.

Рассмотрим следующий код:

class UselessUtilityClass
{
   void AppendWithUnderScore(Person p)
   {
     p.Name = p.Name + "_";
   }
}

и затем где-то мы делаем:

Person p = new Person();
p.Name = "Priest";
UselessUtilityClass u = new UselessUtilityClass();
u.AppendWithUnderScore(p);

Person - это ссылочный тип, когда он передается в UselessUtilityClass - это то, куда я иду - орехи ... VARIABLE p, который является экземпляром ссылки на Person, передается VALUE , что означает, что когда я напишу p.Name, я увижу "Priest _"

А потом, если бы я написал

Person p2 = p;

И я делаю

p2.Name = "Не священник";

И напишите имя p, как показано ниже. Я получу "Not the Priest"

Console.WriteLine(p.Name) // will print "Not a Priest"

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

Правильно ли мое понимание?

Я думаю, что происходит некоторое недопонимание, когда люди говорят, что Все объекты в .NET передаются по ссылке , это не зависит от того, что я думаю. Я могу ошибаться, вот почему я пришел в Stackers.

Ответы [ 6 ]

27 голосов
/ 24 января 2010

Типы значений, такие как int и т. Д., Живут в стеке. Ссылочные типы живут в управляемой куче , однако , если ссылочный тип имеет, например, переменную экземпляра типа double, он будет жить вместе со своим объектом в куче

Нет, это не правильно. Правильное утверждение: «Локальные переменные и формальные параметры типа значения, которые не находятся ни непосредственно в блоке итератора, ни закрытые внешние переменные лямбда-выражения или анонимного метода, размещены в системном стеке исполняющего потока в реализации Microsoft CLI и реализация Microsoft C #. "

Нет требования, чтобы любая версия C # или любая версия CLI использовали системный стек для чего-либо. Конечно, мы делаем это, потому что это удобная структура данных для локальных переменных и формальных параметров типа значения, которые не находятся непосредственно в блоке итератора или закрытых внешних переменных лямбда-выражения или анонимного метода.

См. Мои статьи на эту тему для обсуждения (1) почему это детали реализации, и (2) какие преимущества мы получаем от этого выбора реализации, и (3) что ограничивает желание сделать этот выбор реализации вбивает в язык дизайн.

http://blogs.msdn.com/ericlippert/archive/2009/04/27/the-stack-is-an-implementation-detail.aspx

http://blogs.msdn.com/ericlippert/archive/2009/05/04/the-stack-is-an-implementation-detail-part-two.aspx

Person - это ссылочный тип, когда он передается в UselessUtilityClass - вот куда я иду - чокнутый ...

Сделайте глубокий вдох.

Переменная - это место хранения. Каждое место хранения имеет связанный тип.

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

Место хранения, связанный тип которого является типом значения, всегда содержит объект этого типа.

Значение переменной равно содержимому места хранения .

VARIABLE p, который является экземпляром ссылки Person, передается VALUE,

Переменная p является местом хранения. Он содержит ссылку на экземпляр Person. Следовательно, значение переменной является ссылкой на Person. Это значение - ссылка на экземпляр - передается вызываемому объекту. Теперь другая переменная, которую вы также смущенно назвали «p», содержит то же значение - значение является ссылкой на определенный объект.

Теперь также можно передать ссылку на переменную , что многих людей смущает. Лучший способ думать об этом, когда вы говорите

void Foo(ref int x) { x = 10; }
...
int p = 3456;
Foo(ref p);

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

Имеет смысл сейчас?

8 голосов
/ 24 января 2010

Типы значений, такие как int и т. Д., Живут в стеке, типы ссылок размещаются в управляемой куче, однако, если, например, ссылочный тип имеет переменную экземпляра типа double, он будет жить вместе со своим объектом в куче.

Правильно.

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

переменная VARIABLE p, которая является экземпляром ссылки Person, передается значением VALUE

. На самом деле переменная не является экземпляром класса.Переменная является ссылкой на экземпляр класса.Ссылка передается по значению, что означает, что вы передаете копию ссылки.Эта копия все еще указывает на тот же экземпляр, что и исходная ссылка.

Я думаю, что происходит некоторое недопонимание, когда люди говорят, что все объекты в .NET передаются по ссылке

Да, это определенно недоразумение. Все параметры передаются по значению (если только вы не используете ключевые слова ref или out для передачи их по ссылке).Передача ссылки - это не то же самое, что передача по ссылке .

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

1 голос
/ 24 января 2010

Может быть, в некоторых примерах можно показать различия между ссылочными типами и типами значений, а также между передачей по ссылке и передачей по значению:

//Reference type
class Foo {
    public int I { get; set; }
}

//Value type
struct Boo {
    //I know, that mutable structures are evil, but it only an example
    public int I { get; set; }
}


class Program
{
    //Passing reference type by value
    //We can change reference object (Foo::I can changed), 
    //but not reference itself (f must be the same reference 
    //to the same object)
    static void ClassByValue1(Foo f) {
        //
        f.I++;
    }

    //Passing reference type by value
    //Here I try to change reference itself,
    //but it doesn't work!
    static void ClassByValue2(Foo f) {
        //But we can't change the reference itself
        f = new Foo { I = f.I + 1 };
    }

    //Passing reference typ by reference
    //Here we can change Foo object
    //and reference itself (f may reference to another object)
    static void ClassByReference(ref Foo f) {
        f = new Foo { I = -1 };
    }

    //Passing value type by value
    //We can't change Boo object
    static void StructByValue(Boo b) {
        b.I++;
    }

    //Passing value tye by reference
    //We can change Boo object
    static void StructByReference(ref Boo b) {
        b.I++;
    }

    static void Main(string[] args)
    {
        Foo f = new Foo { I = 1 };

        //Reference object passed by value.
        //We can change reference object itself, but we can't change reference
        ClassByValue1(f);
        Debug.Assert(f.I == 2);

        ClassByValue2(f);
        //"f" still referenced to the same object!
        Debug.Assert(f.I == 2);

        ClassByReference(ref f);
        //Now "f" referenced to newly created object.
        //Passing by references allow change referenced itself, 
        //not only referenced object
        Debug.Assert(f.I == -1);

        Boo b = new Boo { I = 1 };

        StructByValue(b);
        //Value type passes by value "b" can't changed!
        Debug.Assert(b.I == 1);

        StructByReference(ref b);
        //Value type passed by referenced.
        //We can change value type object!
        Debug.Assert(b.I == 2);

        Console.ReadKey();
    }

}
1 голос
/ 24 января 2010

Когда вы передаете человека, он делает копию ссылки - не путайте это с копией объекта. Другими словами, он создает вторую ссылку на тот же объект, а затем передает его.

Когда вы передаете ссылку ref (с ключевым словом ref / out), она передает ту же ссылку на объект, который вы используете в вызывающей стороне, вместо создания копии ссылки.

0 голосов
/ 24 января 2010

В спецификациях ничего не говорится о том, где размещать типы значений и объекты. Правильно было бы реализовать реализацию C #, чтобы распределить все в куче, и в ситуациях Atr, где значения выделяются в куче, кроме тех, которые вы пишете.

int i = 4; Func delete = () => (object) i;

В результате (копия) i будет размещена в куче, потому что компилятор превратит ее в член класса, даже если он не объявлен как таковой. Кроме того, вы в значительной степени на месте. И не все не передаются в качестве ссылки. Было бы ближе к истине утверждать, что каждый параметр был передан по значению, но все еще не совсем корректен (например, ref или out).

0 голосов
/ 24 января 2010

Термин «передача по значению» немного вводит в заблуждение.

Есть две вещи, которые вы делаете:

1) передача ссылочного типа (Person p) в качестве параметра методу

2) установка переменной типа refence (Person p2) в уже существующую переменную (Person p)

Давайте рассмотрим каждый случай.

Дело 1

Вы создали Person p, указывающий на местоположение в памяти, назовем это местоположение x. Когда вы входите в метод AppendWithUnderScore, вы запускаете следующий код:

p.Name = p.Name + "_"; 

Вызов метода создает новую локальную переменную p, которая указывает на то же место в памяти: x. Итак, если вы измените p внутри вашего метода, вы изменит состояние p.

Однако, внутри этого метода, если вы установите p = null, то вы не обнуляете p вне метода. Такое поведение называется «передача по значению»

Дело 2

Этот случай похож на описанный выше случай, но немного отличается. Когда вы создаете новую переменную p2 = p, вы просто говорите, что p2 ссылается на объект в месте p. Так что теперь, если вы измените p2, вы модифицируете p, поскольку они ссылаются на один и тот же объект. Если вы теперь скажете, что p2 = null, то теперь p также будет нулевым. Обратите внимание на разницу между этим поведением и поведением внутри вызова метода. Эта поведенческая разница показывает, как работает «передача по значению» при вызове методов

...