Являются ли структуры «передаваемыми по стоимости»? - PullRequest
32 голосов
/ 12 февраля 2012

Я недавно пытался создать свойство для поля Vector2, просто чтобы понять, что оно не работает должным образом.

public Vector2 Position { get; set; }

это мешает мне изменить значения его членов(X & Y)

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

Как Java-разработчик это меня смущает.

Когда объекты в C # передаются по значению и когда они передаются по ссылке? Передаются ли все объекты структуры по значению?

Ответы [ 9 ]

71 голосов
/ 12 февраля 2012

Важно понимать, что все в C # передается значением , если вы не укажете ref или out в подписи.

Что отличает типы значений (и, следовательно, struct s) от ссылочных типов, так это то, что доступ к типу значений осуществляется напрямую, а ссылочный тип - через его ссылку. Если вы передаете ссылочный тип в метод, его ссылка , а не само значение, передается по значению.

Чтобы проиллюстрировать это, представьте, что у нас есть класс PointClass и struct PointStruct, определенные аналогично (без учета несущественных деталей):

struct PointStruct { public int x, y; }

class PointClass { public int x, y; }

И у нас есть метод SomeMethod, который принимает эти два типа по значению :

static void ExampleMethod(PointClass apc, PointStruct aps) { … }

Если мы сейчас создадим два объекта и вызовем метод:

var pc = new PointClass(1, 1);
var ps = new PointStruct(1, 1);

ExampleMethod(pc, ps);

… мы можем представить это на следующей диаграмме:

diagram

Поскольку pc является ссылкой, оно не содержит само значение; скорее он ссылается на (неназванное) значение где-то еще в памяти. Это видно по пунктирной границе и стрелке.

Но: для pc и ps, фактическая переменная копируется при вызове метода.

Что произойдет, если ExampleMethod переназначит переменные аргумента внутренне? Давайте проверим:

static void ExampleMethod(PointClass apc, PointStruct aps); {
    apc = new PointClass(2, 2);
    aps = new PointStruct(2, 2);
}

Вывод pc и ps после вызова метода:

pc: {x: 1, y: 1}
ps: {x: 1, y: 1}

ExampleMethod изменил копию значений, и исходные значения не изменились.

По сути, это то, что означает «передача по значению».

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

static void ExampleMethod(PointClass apc, PointStruct aps) {
    apc.x = 2;
    aps.x = 2;
}

Теперь мы наблюдаем следующий результат после вызова метода:

pc: {x: 2, y: 1}
ps: {x: 1, y: 1}

→ Ссылочный объект был изменен, тогда как объект значения не был. Приведенная выше диаграмма показывает, почему это так: для эталонного объекта, даже если pc было скопировано, фактическое значение, которое как pc, так и apc эталон, остается идентичным, и мы можем изменить его с помощью apc. Что касается ps, мы скопировали само фактическое значение в aps; исходное значение не может быть затронуто ExampleMethod.

22 голосов
/ 12 февраля 2012

A struct является типом значения, поэтому оно всегда передается как значение.

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

Термин по ссылке используется при использовании ref илиout ключевые слова для передачи параметра.Затем вы передаете ссылку на переменную, которая содержит значение, вместо передачи значения.Обычно параметр всегда передается значением .

5 голосов
/ 25 марта 2015

Предисловие: C # в то время как управляемый все еще имеет идиомы памяти ядра, созданные C. Память может быть разумно рассмотрена как гигантский массив, где индекс в массиве помечен как «адрес памяти». указатель - это числовой индекс этого массива, который также является памятью адресом . Значения в этом массиве могут быть либо данными, либо указателем на другой адрес памяти. константный указатель - это значение, хранящееся в этом массиве по некоторому индексу, который не может изменить . Адрес памяти по своей природе существует и никогда не может измениться, однако значение, которое находится по этому адресу, всегда может измениться, если оно равно , а не const.

Проходят классом

Класс / ссылочный тип (который включает в себя строку) передается ссылкой на константный указатель. Мутация повлияет на все случаи использования этого экземпляра. Вы не можете изменить адрес объекта. Если вы попытаетесь изменить адрес с помощью присваивания или new, вы фактически создадите локальную переменную, которая имеет то же имя, что и параметр в текущей области.

Пропускать копию

Примитивы / ValueTypes / структуры (строки не являются ни одной, даже если они неискренне представляют их), полностью копируются, когда возвращаются из метода, свойства или принимаются в качестве параметра. Мутация структуры никогда не будет передана. Если структура содержит член класса, то копируется ссылка на указатель. Этот объект-член будет изменчивым.

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

Передача истина ссылка

Требуется использование ключевых слов out или ref.

ref позволит вам изменить указатель объекта new или назначить существующий объект. ref также позволит вам передавать примитив / ValueType / struct по его указателю в памяти, чтобы избежать копирования объекта. Это также позволит вам заменить указатель на другой примитив, если вы назначите ему.

out семантически идентичен ref с одним небольшим отличием. ref параметры должны быть инициализированы там, где out параметры могут быть неинициализированы, поскольку они должны быть инициализированы в методе, который принимает параметр. Это обычно показано в методах TryParse и избавляет от необходимости иметь int x = 0; int.TryParse("5", out x), когда начальное значение x не имеет смысла.

5 голосов
/ 12 февраля 2012
Типы данных

.NET подразделяются на типы value и reference .Типы значений включают int, byte и struct s.К ссылочным типам относятся string и классы.

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

3 голосов
/ 13 февраля 2013

Просто чтобы проиллюстрировать различные эффекты передачи struct против класса через методы:

(примечание: проверено в LINQPad 4 )

Пример

/// via /6323838/yavlyaytsya-li-struktury-peredavaemymi-po-stoimosti
void Main() {

    // just confirming with delegates
    Action<StructTransport> delegateTryUpdateValueType = (t) => {
        t.i += 10;
        t.s += ", appended delegate";
    };

    Action<ClassTransport> delegateTryUpdateRefType = (t) => {
        t.i += 10;
        t.s += ", appended delegate";
    };

    // initial state
    var structObject = new StructTransport { i = 1, s = "one" };
    var classObject = new ClassTransport { i = 2, s = "two" };

    structObject.Dump("Value Type - initial");
    classObject.Dump("Reference Type - initial");

    // make some changes!
    delegateTryUpdateValueType(structObject);
    delegateTryUpdateRefType(classObject);

    structObject.Dump("Value Type - after delegate");
    classObject.Dump("Reference Type - after delegate");

    methodTryUpdateValueType(structObject);
    methodTryUpdateRefType(classObject);

    structObject.Dump("Value Type - after method");
    classObject.Dump("Reference Type - after method");

    methodTryUpdateValueTypePassByRef(ref structObject);
    methodTryUpdateRefTypePassByRef(ref classObject);

    structObject.Dump("Value Type - after method passed-by-ref");
    classObject.Dump("Reference Type - after method passed-by-ref");
}

// the constructs
public struct StructTransport {
    public int i { get; set; }
    public string s { get; set; }
}
public class ClassTransport {
    public int i { get; set; }
    public string s { get; set; }
}

// the methods
public void methodTryUpdateValueType(StructTransport t) {
    t.i += 100;
    t.s += ", appended method";
}

public void methodTryUpdateRefType(ClassTransport t) {
    t.i += 100;
    t.s += ", appended method";
}

public void methodTryUpdateValueTypePassByRef(ref StructTransport t) {
    t.i += 1000;
    t.s += ", appended method by ref";
}

public void methodTryUpdateRefTypePassByRef(ref ClassTransport t) {
    t.i += 1000;
    t.s += ", appended method by ref";
}

Результаты

(из дампа LINQPad)

Value Type - initial 
StructTransport 
UserQuery+StructTransport 
i 1 
s one 


Reference Type - initial 
ClassTransport 
UserQuery+ClassTransport 
i 2 
s two 

//------------------------

Value Type - after delegate 
StructTransport 
UserQuery+StructTransport 
i 1 
s one 


Reference Type - after delegate 
ClassTransport 
UserQuery+ClassTransport 
i 12 
s two, appended delegate 

//------------------------

Value Type - after method 
StructTransport 
UserQuery+StructTransport 
i 1 
s one 


Reference Type - after method 
ClassTransport 
UserQuery+ClassTransport 
i 112 
s two, appended delegate, appended method 

//------------------------

Value Type - after method passed-by-ref 
StructTransport 
UserQuery+StructTransport 
i 1001 
s one, appended method by ref 


Reference Type - after method passed-by-ref 
ClassTransport 
UserQuery+ClassTransport 
i 1112 
s two, appended delegate, appended method, appended method by ref 
2 голосов
/ 12 февраля 2012

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

obj.Position.X = x;
obj.Position.Y = y;

Вы меняете только координаты этой эфемерной копии.

Сделайте это вместо

obj.Position = new Vector2(x, y);

Это не имеет ничего общего со значением или ссылкой. Value2 является типом значения, а get возвращает это значение. Если бы вектор имел ссылочный тип (класс), get вернул бы эту ссылку. return возвращает значения по значению. Если у нас есть ссылочный тип, то эти ссылки являются значениями и возвращаются.

1 голос
/ 12 февраля 2012

Да, структуры наследуются от ValueType и передаются по значению.Это верно и для примитивных типов - int, double, bool и т. Д. (Но не для строки).Строки, массивы и все классы являются ссылочными типами и передаются по ссылке.

Если вы хотите передать структуру по ссылке, используйте ключевое слово ref:

public void MyMethod (ref Vector2 position)

, которое будетпередать struct by-ref и позволить вам изменять ее члены.

1 голос
/ 12 февраля 2012

Объекты передаются по ссылке и структурируются по значению.Но обратите внимание, что у вас есть аргументы "out" и "ref" для аргументов.

Таким образом, вы можете передать структуру по ссылке так:

0 голосов
/ 13 февраля 2012

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

Что касается работы со структурами, хранящимися в коллекции, использование изменяемых структур с существующими коллекциями обычно требует считывания структуры в локальную копию, изменения этой копии и сохранения ее обратно. Предполагая, что MyList равен List<Point>, и нужно добавить некоторую локальную переменную z в MyList[3].X:

  Point temp = MyList[3];
  temp.X += Z;
  MyList[3] = temp;

Это слегка раздражает, но часто чище, безопаснее и эффективнее, чем использование неизменяемых структур, намного эффективнее, чем неизменяемые объекты класса, и намного безопаснее, чем использование изменяемых объектов класса. Мне бы очень хотелось увидеть поддержку компилятора для лучшего способа представления коллекциями элементов типа значения. Существуют способы написания эффективного кода для обработки такого воздействия с хорошей семантикой (например, объект коллекции может реагировать при обновлении элементов, не требуя, чтобы эти элементы имели какую-либо ссылку на коллекцию), но код читается ужасно. Добавление поддержки компилятора концептуально аналогично замыканиям позволит эффективному коду также быть читаемым.

Обратите внимание, что вопреки тому, что утверждают некоторые люди, структура принципиально отличается от объекта типа класса, но для каждого типа структуры существует соответствующий тип, иногда называемый "коробочной структурой" , который происходит от Object (см. спецификацию CLI (Common Language Infrastructure) , разделы 8.2.4 и 8.9.7). Хотя компилятор неявно преобразует любую структуру в соответствующий ей коробочный тип, когда это необходимо, чтобы передать ее в код, который ожидает ссылку на объект типа класса, позволит ссылкам на коробчатые структуры копировать их содержимое в реальные структуры, а иногда и позволяет код для работы непосредственно с коробочными структурами, штучные структуры ведут себя как объекты классов, потому что это то, что они есть.

...