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

Являются ли структуры "сквозными"?

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

public Vector2 Position { get; set; }

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

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

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

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

4b9b3361

Ответ 1

Важно понимать, что все в С# передается значением, если вы не укажете в подписи 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}

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

Ответ 2

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

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

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

Ответ 3

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

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

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

Ответ 4

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

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

Пример

/// via http://stackoverflow.com/questions/9251608/are-structs-pass-by-value
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 

Ответ 5

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

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

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

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

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

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

Перейдите по ссылке true

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

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

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

Ответ 6

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

Итак, вы можете передать структуру по ссылке так:

public void fooBar( ref Vector2 position )
{
}

Ответ 7

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

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

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

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

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

Это не имеет никакого отношения к значению или по ссылке. Value2 - тип значения, а get возвращает это значение. Если вектор является ссылочным типом (классом), get вернет эту ссылку. Возвращаемые значения всегда возвращаются значением. Даже если значения являются ссылками, эти ссылки возвращаются значением.

Ответ 8

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

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

public void MyMethod (ref Vector2 position)

который передаст структуру by-ref и позволит вам изменять ее элементы.

Ответ 9

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

Что касается работы со структурами, хранящимися в коллекции, использование измененных структур с существующими коллекциями обычно требует считывания структуры в локальной копии, изменения ее копии и ее сохранения. Предполагая, что 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). Хотя компилятор будет неявно преобразовывать любую структуру в свой соответствующий тип в виде бокса, когда это необходимо, чтобы передать ее в код, который ожидает ссылки на объект типа класса, позволит ссылаться на вложенные структуры, чтобы их содержимое копировалось в реальные структуры и иногда позволяло код для работы с коробчатыми структурами напрямую, вложенные структуры ведут себя как объекты класса, потому что это то, чем они являются.