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

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

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

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

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

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

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

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

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

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

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

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

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

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

И если я написал

Person p2 = p;

И я делаю

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

И напишите имя p, как показано ниже. Я получу "Не жреца"

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

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

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

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

4b9b3361

Ответ 1

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

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

Нет необходимости, чтобы любая версия С# или любая версия 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

Лицо является ссылочным типом при передаче в 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, потому что они являются двумя именами для одного и того же места хранения.

Теперь чувствуете смысл?

Ответ 2

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

Правильно.

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

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

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

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

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

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

Ответ 3

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

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

Ответ 4

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

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

}

Ответ 5

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

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

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

2) установка переменной типа refension (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 теперь также будет null. Обратите внимание на разницу между этим поведением и поведением внутри вызова метода. Эта поведенческая разница описывает, как "передавать по значению" работает при вызове методов

Ответ 6

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

int я = 4; Func dele =() = > (объект) i;

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