Re:
Это является общепризнанным (в C#, по крайней мере), что при передаче по ссылке, способ содержит ссылку на объект манипулируют, в то время как при передаче по значению, метод копирует значение манипулируют ...
проблема состоит в том, что существуют две различные концепции:
- Типы значений (например, целое) против ссылочных типов (например, строка)
- Передача по значению (поведение по умолчанию) против передачи по ссылке (ссылка, из)
Если вы явно не проходят (любой) переменную по ссылке, используя out
или ref
, параметры передаются значением в C#, независимо от того, является ли переменная типом значения или ссылочным типом.
При прохождении ценности типов (таких, как int
, float
или как DateTime
структур), вызываемая функция получает copy of the entire value type (через стек).
Любое изменение типа значения и любые изменения каких-либо свойств/полей копии будут потеряны при выходе вызываемой функции.
Однако с ссылкой типов (например, string
и пользовательские классы, как ваш MyPoint
класса), это reference
к тому же, общий экземпляр объекта, который копируется и передается в стек.
Это означает, что:
- Любые изменения полей или свойств общего объекта являются постоянными (т.е. любые изменения в
x
или y
)
- Однако сама ссылка еще копируется (принятые значение), поэтому любое изменение копии ссылки будет потеряно. Вот почему ваш код не работает, как ожидалось
Что здесь происходит:
void Replace<T>(T a, T b)
{
a = b;
}
для ссылочных типов T
, означает, что локальная переменная (стек) ссылка на объект a
переназначен на локальный столбец b
. Это переназначение является локальным только для этой функции - как только область выходит из этой функции, повторное назначение теряется.
Если вы действительно хотите, чтобы заменить ссылки звонящих, вам необходимо изменить подпись так:
void Replace<T>(ref T a, T b)
{
a = b;
}
Это изменяет вызов вызова по ссылке - фактически мы передаем адрес от переменной вызывающего объекта к функции, которая затем позволяет , вызванному методом, изменить переменную вызывающего метода.
Однако, в настоящее время:
- Передача по ссылке является generally regarded as a bad idea - вместо этого, мы должны либо передать возвращаемые данные в возвращаемом значении, и если есть более чем одна переменная, возвращается, а затем использовать
Tuple
или пользовательский class
или struct
, который содержит все такие возвращаемые переменные.
- Изменение ('mutating') переменной общего значения (и даже ссылки) в вызываемом методе недовольно, особенно сообществом функционального программирования, поскольку это может привести к сложным ошибкам, особенно при использовании нескольких потоков. Вместо этого отдайте предпочтение неизменяемым переменным, или если требуется мутация, тогда рассмотрите возможность изменения (потенциально глубокой) копии переменной. Вы можете найти интересные темы о «чистых функциях» и «константной корректности».
Редактировать
Эти две диаграммы могут помочь с объяснением.
Пасс значения (ссылочные типы):
В своем первом случае (Replace<T>(T a,T b)
) a
и b
передаются по значению. Для reference types, this means the references копируются в стек и передаются вызываемой функции.
- Ваш исходный код (я назвал это
main
) выделяет два MyPoint
объекты на управляемой куче (я назвал эти point1
и point2
), а затем присваивает две локальные ссылки на переменные a
и b
, чтобы ссылаться на точки, соответственно (светло-голубые стрелки):
MyPoint a = new MyPoint { x = 1, y = 2 }; // point1
MyPoint b = new MyPoint { x = 3, y = 4 }; // point2
Затем вызов Replace<Point>(a, b)
выталкивает копию двух ссылок в стек (красные стрелки). Метод Replace
рассматривает их как два параметра, также называемых a
и b
, которые по-прежнему указывают на point1
и point2
соответственно (оранжевые стрелки).
Назначение, a = b;
затем изменяет локальную a
переменная Replace
методы таким образом, что a
теперь указывает на тот же объект, как ссылается b
(т.е. point2
). Однако обратите внимание, что это изменение относится только к локальным (стек) переменным Replace, и это изменение повлияет только на последующий код в Replace
(синяя линия). Он НЕ влияет на ссылки на переменные вызывающей функции каким-либо образом, NOR делает это, изменяя объекты point1
и point2
на кучу вообще.
Pass по ссылке:
Однако, если мы изменим вызов Replace<T>(ref T a, T b)
, а затем изменить main
передать a
по ссылке, т.е. Replace(ref a, b)
:
Как и раньше, две точки obj которые выделяются в куче.
Теперь, когда Replace(ref a, b)
называется, в то время как main
сек ссылка b
(указывая на point2
) все еще копируется во время разговора, a
теперь передается по ссылке, а это означает, что «адрес» в a
переменном Мэйне передаются до Replace
.
Теперь, когда задание a = b
сделан ...
Это функция вызова, main
«s a
ссылка на переменную, которая в настоящее время обновляется, чтобы ссылаться на point2
. Изменение, сделанное повторным назначением на a
, теперь видно как main
, так и Replace
. Там сейчас нет ссылки на point1
Изменения в куче объектов воспринимаемым Все ссылки
В обоих вышеуказанных сценариев, никаких изменений не было на самом деле сделал для объектов кучи, point1
и point2
, это было только локальные ссылки на переменные, которые были переданы и повторно назначены.
Однако если какие-либо изменения были внесены в объекты кучи point1
и point2
, то все переменные ссылки на эти объекты будут видеть эти изменения.
Так, например:
void main()
{
MyPoint a = new MyPoint { x = 1, y = 2 }; // point1
MyPoint b = new MyPoint { x = 3, y = 4 }; // point2
DoSomething(a, b);
// a and b are Changed!
Assert.AreEqual(53, a.x);
Assert.AreEqual(21, b.y);
}
public void DoSomething(MyPoint a, MyPoint b)
{
a.x = 53;
b.y = 21;
}
Теперь, когда исполнение возвращается к main
, все ссылки на point1
и point2
, в том числе main's
переменных a
и b
, которые теперь будут «видеть» изменения, когда они рядом чтения значения для x
и y
баллов.Вы также заметите, что переменные a
и b
по-прежнему передавались по значению DoSomething
.
Изменение типов значений влияет на локальную копию только
Типы значений (примитивы как System.Int32
, System.Double
и структуры, как System.DateTime
) выделяются в стеке, а не кучи, и копируются на стопку при перешел в звонок. Одно из отличий здесь в том, что изменения, вызванные вызываемой функцией для поля типа значения или свойства, будут наблюдаться локально только вызываемой функцией, потому что это будет только мутировать локальную копию типа значения.
например. Рассмотрим следующий код экземпляра изменчивой структуры, System.Drawing.Rectangle
public void SomeFunc(System.Drawing.Rectangle aRectangle)
{
// Only the local SomeFunc copy of aRectangle is changed:
aRectangle.X = 99;
// Passes - the changes last for the scope of the copied variable
Assert.AreEqual(99, aRectangle.X);
} // The copy aRectangle will be lost when the stack is popped.
// Which when called:
var myRectangle = new System.Drawing.Rectangle(10, 10, 20, 20);
// A copy of `myRectangle` is passed on the stack
SomeFunc(myRectangle);
// Test passes - the caller's struct has NOT been modified
Assert.AreEqual(10, myRectangle.X);
выше, может быть довольно запутанным и основные моменты, почему это хорошая практика, чтобы создавать свои собственные, как неизменны структур.
Ключевое слово ref
работает так же, как и переменные типа значения, которые передаются по ссылке, а именно: «адрес» переменной типа значения вызывающего объекта передается в стек, а назначение назначенной переменной вызывающего абонента теперь возможно.
Ни один из ваших примеров не использует 'ref' или' out', поэтому все они передают (ссылку) по значению. – CodesInChaos