2010-02-19 3 views
1

Скажем, у меня есть класс:Как сделать оператор + = сохранить ссылку на объект?

class M 
{ 
    public int val; 

а также + оператор внутри него:

public static M operator +(M a, M b) 
    { 
     M c = new M(); 
     c.val = a.val + b.val; 
     return c; 
    } 
} 

И у меня есть List объектов класса:

List<M> ms = new List(); 
M obj = new M(); 
obj.val = 5; 
ms.Add(obj); 

Некоторые другие объекты:

M addie = new M(); 
addie.val = 3; 

Я могу это сделать:

ms[0] += addie; 

и это, безусловно, работает, как я ожидал - значение в списке изменяется. Но если я хочу сделать

M fromList = ms[0]; 
fromList += addie; 

это не меняет значение в ms по очевидным причинам.

Но интуитивно я ожидаю, что ms[0] также изменится после этого. Действительно, я выбираю объект из списка, а затем увеличиваю его значение с каким-то другим объектом. Итак, так как я держал ссылку на ms[0] в fromList перед добавлением, я хочу еще держать его в fromList после выполнения его.

Есть ли способы достичь этого?

+0

не должен из списка просто быть какой-то ссылкой здесь? Я не знаю достаточно C#, хотя, чтобы рассказать вам, как это сделать. – falstro

+0

'ms [0] = ms [0] + = addie;' это то, что я считаю нормальным кодом – Will

ответ

6

Вы не должны ожидать ms[0] изменить. После того, как он был инициализирован, fromList не связан с ms - fromList и ms[0] имеют одинаковое значение, но это все. + = возвращает новое значение (как и должно быть), поэтому вы просто меняете значение, хранящееся в fromList, которое полностью не зависит от значения в списке. (Обратите внимание, что здесь указаны значения, не объектов.)

Не пытайтесь изменить это поведение - это правильно. Вместо этого измените свои ожидания.

Если вы действительно хотите, чтобы содержимое списка отражало изменение, вам нужно либо изменить значение в списке, чтобы обратиться к новому объекту, либо вам нужно изменить свой код, чтобы вместо этого изменить существующий объект создания нового. Если вы примете последний подход, вы должны не сделать это внутри оператора. Вместо этого создайте Add метод, так что вы бы назвали:

fromList.Add(addie); 

Это довольно ясно, что это операция Mutating, поэтому он не будет нарушать любые ожидания.

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

+0

, пока это правда, нет ли указателей или ссылок в C# для решения этой проблемы? – falstro

+0

+1, с использованием перегрузки оператора для тонкого изменения значения оператора, как правило, очень плохая идея. –

+1

@roe: Есть ссылки, но не такие, о которых вы думаете об ИМО. Оба списка и переменная будут иметь ссылку на объект - объект не сохраняется в списке напрямую. –

0

Старайтесь избегать применения операторов. В большинстве случаев это не стоит проблем и делает код более запутанным. Вместо этого используйте простые методы add/substract. Кроме того, если вы добавите другие значения внутри своего объекта, это будет проще расширить.Используя оператор, вы используете семантику, связанную со всем объектом, чтобы воздействовать на ее часть. Операторы обычно имеют больше смысла при использовании со структурой. Структуры - это то, что вы должны использовать при реализации типов, которые должны вести себя как типы значений.

+0

Если ваш объект можно добавить/вычесть, например. если он представляет что-то, что ведет себя как число, то имеет смысл перегружать операторов. Кроме того, если у вас есть что-то, что ведет себя как индексированный контейнер, имеет смысл обеспечить реализацию индексатора, эффективно перегружая оператор '[]'. И это относится к классам так же, как (если не больше) к структурам. –

4

Вы смешиваете изменчивое поведение с неизменным поведением, я думаю, что это то, где он запутывается.

Объект изменен (т. Е. Вы можете изменить его свойства), и он ведет себя так, как вы ожидали бы при назначении значения его свойства.

Когда вы используете оператор +, он вместо этого ведет себя как неизменяемое значение. Скажем, что у вас есть список целых чисел и прочитайте целое число в переменной. Вы бы не expext целого числа в списке, чтобы изменить, если вы изменили значение переменного:

List<int> list = new List<int>() { 1, 2, 3}; 
int x = list[0]; 

// this will of cousre not change the content in the list: 
x += 42; 

Если у вас есть этот код:

M fromList = ms[0]; 
fromList += addie; 

Компилятор использует оператор +, как это:

M fromList = ms[0]; 
fromlist = fromList + addie; 

выражение fromlist + addie возвращает ссылку на новый экземпляр класса, и эта ссылка присваивается переменной. Естественно, это не изменит объект, к которому ранее ссылалась переменная.

0

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

В C# сложные операторы присваивания называют простой оператор, который помещает результат в новый экземпляр, а затем изменяет исходную переменную, ссылаясь на новый экземпляр. Исходный референт не затрагивается.

В C++ сложные агенты присваивания мутируют референт. В большинстве случаев простой арифметический оператор начинается с клонирования LHS во временное, а затем вызывает сложный оператор присваивания, изменяя временную.

Таким образом, простые операторы работают одинаково в обоих, давая новый экземпляр, но сложные операторы присваивания всегда создают новый экземпляр в C#, а не на C++.

+0

Это не просто * составное задание, то другое. Простое назначение слишком сильно отличается, потому что объекты C++ имеют семантику копирования (например, C# structs), а C# предпочитает ссылочную семантику. – dan04

+0

@dan: Я хотел сказать «простые арифметические» операторы, а не «простое назначение». Теперь исправлено. Ваше утверждение о простом назначении верно только для ссылочных типов, на которые вы намекали. Конечно, ссылочные типы очень похожи на указатели C++ (но автоматически разыменовываются, за исключением случаев, когда они назначаются). –

0

Настоящий ответ заключается в том, что его оператор + не работает на объекте lhs, он копирует и увеличивает копию. Если его оператор + действительно изменил объект, он изменит значение в списке.

после

M fromlist = ms[0]; 

fromlist и мс [0] указывают на один и тот же объект. В настоящее время

fromlist += addie; 

фактически делает

fromlist = fromlist.op+(addie); 

оп + возвращает новый М с Валу = fromlist.val + addie.val

так что теперь fromlist и мс [0] указывают на разные вещи с разными значения

0

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

В C++, как вы сделать массив типа класса путем перегрузки [] (известные как оператор индексации):

std::string operator[](int nIndex); 

Но это определение только позволяет читать элементы. Для поддержки письма, вы должны вернуть то, что мы C++ Fanboys скоро придется начать называть lvalue reference:

std::string& operator[](int nIndex); 

Это означает (по крайней мере, в данном контексте), что он может появиться на левой стороне выражения присваивания:

list[3] = "hi"; 

Но это вызывает все виды страшных жизненных проблем. Если вы можете получить ссылку, как это, вы можете связать его с именем:

std::string& oops = list[3]; 

Теперь oops относится к чему-то в внутренностей list. Это не очень безопасная ситуация, потому что некоторые операции на list приведут к тому, что oops станет тикающей бомбой замедленного действия, и вам нужно иметь подробные сведения о том, как работает list, чтобы этого избежать.

C# работает по-разному. Дизайнер из массива, как класс получает для определения двух отдельных определений для оператора [], как часть индексатор (# имя C для реализации оператора индексного):

public string this[int index] 
{ 
    get { /* return a string somehow */ } 
    set { /* do something with implicit 'value' param */ } 
} 

Это аккуратно обходит требуется для ссылок на lvalue. Это также означает, что ваш простой пример, который работает на самом деле включает в себя некоторые ловкий компилятор ног:

ms[0] += addie; 

Как и многие другие ответы сказали, это осуществляется с помощью обычного + оператора, поэтому он расширяется:

ms[0] = ms[0] + addie; 

Но эти два события ms[0] фактически звонки на совершенно разные методы. Это немного напоминает:

ms.SetAt(0, ms.GetAt(0) + addie); 

Так вот почему этот пример работает. Он заменяет объект, хранящийся в списке. Это тот шаг, который отсутствует в вашем нерабочем примере.

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