5

Рассмотрим следующий код (я имею целенаправленно письменное MyPoint быть ссылочного типа для данного примера)C# передача по значению против прохода по ссылке

public class MyPoint 
{ 
    public int x; 
    public int y; 
} 

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

Пример:

void Replace<T>(T a, T b) 
{ 
    a = b; 
} 

int a = 1; 
int b = 2; 

Replace<int>(a, b); 

// a and b remain unaffected in global scope since a and b are value types. 

Вот моя проблема; MyPoint является ссылочным типом, поэтому я ожидал бы такую ​​же операцию на Point, чтобы заменить a на b в глобальном масштабе.

Пример:

MyPoint a = new MyPoint { x = 1, y = 2 }; 
MyPoint b = new MyPoint { x = 3, y = 4 }; 

Replace<MyPoint>(a, b); 

// a and b remain unaffected in global scope since a and b...ummm!? 

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

+1

Ни один из ваших примеров не использует 'ref' или' out', поэтому все они передают (ссылку) по значению. – CodesInChaos

ответ

7

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 копируются в стек и передаются вызываемой функции.

enter image description here

  1. Ваш исходный код (я назвал это main) выделяет два MyPoint объекты на управляемой куче (я назвал эти point1 и point2), а затем присваивает две локальные ссылки на переменные a и b, чтобы ссылаться на точки, соответственно (светло-голубые стрелки):

MyPoint a = new MyPoint { x = 1, y = 2 }; // point1 
MyPoint b = new MyPoint { x = 3, y = 4 }; // point2 
  1. Затем вызов Replace<Point>(a, b) выталкивает копию двух ссылок в стек (красные стрелки). Метод Replace рассматривает их как два параметра, также называемых a и b, которые по-прежнему указывают на point1 и point2 соответственно (оранжевые стрелки).

  2. Назначение, 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):

enter image description here

  1. Как и раньше, две точки obj которые выделяются в куче.

  2. Теперь, когда Replace(ref a, b) называется, в то время как main сек ссылка b (указывая на point2) все еще копируется во время разговора, a теперь передается по ссылке, а это означает, что «адрес» в a переменном Мэйне передаются до Replace.

  3. Теперь, когда задание a = b сделан ...

  4. Это функция вызова, 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 работает так же, как и переменные типа значения, которые передаются по ссылке, а именно: «адрес» переменной типа значения вызывающего объекта передается в стек, а назначение назначенной переменной вызывающего абонента теперь возможно.

3

C# фактически проходит по значению. Вы получаете иллюзию, что он передается по ссылке, потому что когда вы передаете ссылочный тип, вы получаете копию ссылки (эта ссылка была передана по значению). Однако, поскольку ваш метод замещения заменяет эту ссылочную копию другой ссылкой, она фактически ничего не делает (скопированная ссылка сразу выходит из области действия). Вы действительно можете пройти по ссылке, добавив ключевое слово ref:

void Replace<T>(ref T a, T b) 
{ 
    a = b; 
} 

Это поможет вам ваш желаемый результат, но на практике это немного странно.

1

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

Прочитать this great piece от Jon Skeet для более глубокого понимания.

0

Вы не понимаете, что означает обращение через ссылку. Ваш метод Replace создает копию объекта Point - проходящего по значению (что на самом деле является лучшим способом сделать это).

Чтобы передать ссылку, чтобы a и b ссылались на одну и ту же точку в памяти, вам нужно добавить «ref» к подписи.

+0

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

0

Вы не понимаете это правильно.

Это похоже на Java - все передается по значению! Но вы должны знать, какова ценность.

В примитивных типах данных это значение является самим номером. В других случаях это ссылка.

НО, если вы копируете ссылку на другую переменную, она содержит ту же ссылку, но не ссылается на переменную (таким образом, она не проходит по ссылке, известной в C++).

1

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

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

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

public void Func(Point p){p.x = 4;} 
Point p = new Point {x=3,y=4}; 
Func(p); 
// p.x = 4, p.y = 4 

Теперь давайте посмотрим на этот метод:

public void Func2(Point p){ 
p = new Point{x=5,y=5}; 
} 
Func2(p); 
// p.x = 4, p.y = 4 

Так не изменился не произошло здесь, и почему? Ваш метод просто создал новую точку и изменил ссылку p (которая была передана по значению), и поэтому изменение было локальным. Вы не манипулировали точкой, вы изменили ссылку, и вы сделали ее локально.

И приходит ref ключевое слово, которое экономит день:

public void Func3(ref Point p){ 
p = new Point{x=5,y=5}; 
} 
Func3(ref p); 
// p.x = 5, p.y = 5 

То же самое происходило в вашем примере. Вы назначили точку с новой ссылкой, но вы сделали ее локально.