2016-11-24 2 views
2

Я понимаю, что C# ref позволяет отправлять указатель на объект. Мне просто интересно, что это значит для того, что на самом деле отправляется на функцию под капотом.Техническое значение C# ref

Насколько я понимаю, это как:

DoSomething (ссылка exampleObject) -> здесь указатель exampleObject отправляется на функцию

DoSomething (exampleObject) -> здесь массив указателей на свойства объекта отправляется в функцию.

Это правильно? И будет ли вторая функция создавать указатель на типы значений? (Наблюдение, поскольку они являются свойствами, и их значение должно быть сохранено в куче, чтобы сохранить ссылочную целостность объекта)

Немного понятнее объяснение: * Объект А находится в ячейке памяти 5 * Он имеет свойства объекта B и объекта C

Теперь я вызываю DoSomething (A) -> поскольку объекты обрабатываются с помощью указателей по умолчанию, я передаю значение указателя 5, чтобы я мог манипулировать значением в адресе памяти 5. Как знает ли C#, на каком адресе памяти находятся свойства отправленного объекта?

Теперь я вызываю DoSomething (ref A) -> В этот момент я создаю новый указатель (скажем, 6), который указывает на другой указатель. Итак, теперь я перехожу в 6, что позволяет мне изменить значение адреса памяти 6, которое обычно оказывается равным 5, местоположение объекта A. Теперь я могу изменить память, к которой был передан объект A, указывает на ,

Что я не понимаю, так это как свойства находятся в памяти? Где-то должен посылаться массив указателей или указатель на массив указателей, правильно?

+0

«Я понимаю, что C# ref позволяет отправлять указатель на объект». - это не то, что это значит ... когда вы назначаете 'var obj = GetSomeObject();' - *, который * присваивает ссылку (которая является * по существу * fancy-talk для указателя) объекту –

+5

exampleObject уже является указатель. Он указывает на объект, хранящийся в куче. Поэтому 'ref' дает вам указатель на указатель. Эта деталь, которая использовалась для заполнения половины класса программирования 101 до конца семестра. Нет «массива указателей», указатель, хранящийся в exampleObject, достаточен для нахождения членов. –

+0

@Hans Passant: Я получаю это, но если нет массива указателей, как можно ссылаться на все свойства объекта? Нужно знать места их памяти, чтобы изменить их ценности, не так ли? – Kai

ответ

1

В моем понимании во втором случае

DoSomething(exampleObject) 

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

DoSomething(ref exampleObject) 

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

+0

Вот пример в действии: http://ideone.com/ lBSjLJ – CompuChip

+0

Если адрес объекта отправлен (как вы сказали о функции без ссылки), я должен иметь возможность изменить значение объекта, правильно? (Так как вы можете изменить значение адреса памяти, на которое указывает указатель) Но я не могу, я могу только изменить его свойства. Это означает, что для вызываемой функции доступны только адреса свойств. Вот почему я предполагаю, что C# отправляет массив указателей (точнее указатель на массив указателей) под капотом при отправке объекта. Итак, что именно отправляется? – Kai

1

Нет, если вы пришли в C# от C++ вы можете себе представить реф в качестве ссылки в C++, как, что:

void foo(A*& a) 
{ 
    // Here you can change object's state 
    // Also you can change value of pointer 
    // Caller will have new value of pointer 
} 
void foo(A* a) 
{ 
    // Here you can change object's state 
    // Also you can change value of copy of pointer 
    // Caller will have old value of pointer 
} 
0

Мы можем использовать DoSomething(ref exampleObject), если мы знаем, или ожидать, что DoSomething может совершить действие «рассчет значение или создание нового объекта и возврат его нам ». И что мы знаем, что объявление DoSomething также содержит ключевое слово ref.

Если перед вызовом ваш exampleObject содержит определенное значение, или это указывает на определенный объект (или даже нуль), то после того, как называют его может содержать новое значение, или может быть направлен на новый объект или он может был изменен на null.

Там нет уверенности или обязательства, чтобы это произошло с ref, только что еслиDoSomething решает, что exampleObject следует изменить, то мы получим, что новое значение или объект (или нуль). И в противном случае мы сохраняем то, что было раньше.

Это верно для обоих типов значений (числа, bools, structs) и ссылочных типов (объектов), поэтому я продолжал говорить «новое значение или новый объект или нуль».

Edit:
я сознательно избегал упоминать указатели. Если вы не делаете материал низкого уровня, в C# /. NET вы почти никогда не должны знать о указателях. Обычно достаточно понимать понятия и правила, которые ими управляют.

0

Я всегда сравниваю указатели на куклы вуду - все, что вы делаете с куклой вуду, повлияет на человека, которого кукла вуду «указывает» на.

так C# ссылка похожа на куклу вуду - она ​​прикрепляется к объекту, и вы можете воздействовать на объект, потребляя куклу вуду.

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

Каково решение? создавая еще одну куклу вуду - в оригинальную куклу вуду! таким образом я могу возиться с оригинальной куклой и не только с человеком, на который он указывает.

так ref в C# является ссылкой на ссылку - он позволяет вам редактировать ссылку, а не только объект, на который он указывает.

За кулисами ссылка - это просто номер, который указывает, где находится объект в ОЗУ. a ref ссылка - это еще одно число, но указывает, где исходная ссылка находится в ОЗУ, а не где находится объект, на который он указывает.

+0

Почему вы считаете, что кукла-вуду, указывающая на куклу вуду, не может смириться с человеком, которому также являются вторая точка кукол-вуду? И если вы считаете, что каждый 'ref' - это указатель (« только номер, который указывает, где находится объект в ОЗУ ») ** на исходную ссылку на объект **, а не на сам объект, тот же вопрос, что и у Hans Passant - как насчет ссылки на ref для ссылки? – conio

+0

@conio Я согласен, что лучшая фраза будет «не только с», исправлена –

0

No, это неправильный номер.

ref Приводит к таблице эталонная семантика.

При вызове функции foo(T arg) - независимо от того, T является типом значения или ссылочный тип (я не могу не подчеркнуть, что достаточно) - foo получает копию T.

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

Некоторых изображений я нашел на Google вместо рисования один на моем собственном:

enter image description here

Копия в изображении, в основном то, что происходит, когда вы называете foo(T arg) это говорит. В обоих случаях мы arg содержит копию того, что было передано foo. Если T является int этого числа вы заинтересованы. Если T является Circle то arg является «адресом» , что аргумент, передаваемый foo содержал. Вместо того, чтобы говорить о указателях или ссылках, мы можем говорить о стрелках. переменные ссылочных типов содержат стрелки для интересного материала, в то время как переменные типов значений содержат сам материал.

Это демонстрируется следующим кодом:

using System; 
class MainClass { 
    public static void Main (string[] args) { 
    Foo foo1 = new Foo(); 
    foo1.i = 1; 
    Console.WriteLine("Main(): foo1.i: {0}", foo1.i); 

    doStuffToFoo(foo1, 5); 
    Console.WriteLine("Main(): foo1.i: {0}", foo1.i); 

    Console.WriteLine(); 

    Bar bar1; 
    bar1.j = 1; 
    Console.WriteLine("Main(): bar1.j: {0}", bar1.j); 

    doStuffToBar(bar1, 5); 
    Console.WriteLine("Main(): bar1.j: {0}", bar1.j); 
    } 

    static void doStuffToFoo(Foo aFoo, int aInt) { 
    aFoo.i = aInt; 
    aFoo = new Foo(); 
    aFoo.i = 123; 
    Console.WriteLine("doStuffToFoo: aFoo.i: {0}", aFoo.i); 
    } 

    static void doStuffToBar(Bar aBar, int aInt) { 
    aBar.j = aInt; 
    aBar = new Bar(); 
    aBar.j = 123; 
    Console.WriteLine("doStuffToBar: aBar.j: {0}", aBar.j); 
    } 
} 

class Foo { 
    public int i; 
} 

struct Bar { 
    public int j; 
} 

Смотрите, если вы понимаете, что он печатает, прежде чем пытаться или читать дальше.

Переменная aFoo внутри doStuffToFoo является копия из foo1. Легко и просто. aFoo и foo1 являются различными переменными (хранящимся в различных местах на стеке - foo1 в Main «кадра стека с и aFoo в doStuffToFoo» кадра стека с - при условии что стопка, так как the stackis an implementationdetail), и они оба содержат стрелку к такой же Foo объект.

Когда вы ссылаетесь на поля обеих этих переменных, они получают доступ к тому же объекту Foo через соответствующие стрелки. Но когда вы назначаете new Foo() на aFoo, он меняет только стрелку в aFoo, чтобы указать на новый объект Foo. Он ничего не делает с другой стрелкой к первому объекту Foo, который безопасно хранится внутри переменной foo1 и недоступен для кода в doStuffToFoo. Вот почему вы не видите «123» назад в Main.

И все это приводит нас к ref. ref T v говорит, что v не является новой переменной типа T (которая содержит некоторые интересные вещи, если T - тип значения или стрелка для некоторых интересных вещей, если T является ссылочным типом). Скорее v является псевдоним тому, что передается как аргумент. Это второе имя для некоторой другой переменной.

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

/***** inside Main: *****/ 
Foo foo2 = new Foo(); 
Foo foo3 = foo2; 
foo2.i = 1; 
Console.WriteLine("Main(): foo2.i: {0}", foo2.i); 
Console.WriteLine("Main(): foo3.i: {0}", foo3.i); 

doStuffToFooRef(ref foo2, 5); 
Console.WriteLine("Main(): foo2.i: {0}", foo2.i); 
Console.WriteLine("Main(): foo3.i: {0}", foo3.i); 
/***** until here *****/ 

static void doStuffToFooRef(ref Foo aFoo, int aInt) { 
    aFoo.i = aInt; 
    aFoo = new Foo(); 
    aFoo.i = 123; 
    Console.WriteLine("doStuffToFooRef: aFoo.i: {0}", aFoo.i); 
} 

Теперь aFoo это другое название для foo2. Когда вы назначаете «aFoo», вы назначаете «foo2», поскольку оба эти имени относятся к одной и той же переменной (место хранения). Это то, что происходит во время выполнения задания в doStuffToFooRef:

enter image description here

(i по-прежнему равна нулю, так как это, прежде чем положить туда 123)

Попробуйте здесь: https://repl.it/E80a

C# ref, как C++ ссылаясь на это, как писал Александр Лысенко в своем ответе. В то время как технически ref SomeReferenceType xможет быть «указатель на указатель», как утверждают некоторые, это не улавливает реальный смысл ref и останавливается на деталях реализации, которые не обязательно всегда верны (это может быть оптимизировано) , Это означает, что добавление ref добавляет указатель. Итак, ref-of-a-ref ссылочного типа является указателем на указатель на указатель? Каждый дополнительный ref добавляет уровень косвенности?


Для целей этой дискуссии мы не заботимся, если это действительно адрес или что-то другое. Поскольку объекты могут перемещаться в памяти, мы понимаем, что это, вероятно, не адрес виртуальной памяти хост-машины/операционной системы, но это адрес в широком смысле.
Некоторые делают различия между ссылками и указателями, последние представляют собой числовые адреса памяти, но другие не боятся называть эти указатели тоже, потому что они указывают на объекты. Например, спецификация языка Java (§4.3.1) прямо говорит:

Эталонные значения (часто просто ссылка) являются указатели на эти объекты, а также специальная ссылка нулевой, которая относится ни к какому объекту.

Они подчеркивают «ссылки» - новый термин, который они вводят, но какие указатели очевидны для читателя, по их мнению, и там тоже объекты не являются неотъемлемыми AFAIK.

Смежные вопросы