Почему операторы присваивания возвращают значение?

Это разрешено:

int a, b, c;
a = b = c = 16;

string s = null;
while ((s = "Hello") != null) ;

Насколько я понимаю, назначениеs = ”Hello”; должен только вызвать“Hello” быть назначенным наs, но операция не должна возвращать никакого значения. Если это было правдой, то((s = "Hello") != null) выдаст ошибку, так какnull будет по сравнению с ничем.

Какова причина, позволяющая операторам присваивания возвращать значение?

 Hans Passant27 сент. 2010 г., 21:52
Стандарт для языков в ветке C. Задания являются выражениями.
 Jim Balter23 июл. 2015 г., 23:40
«присваивание ... должно вызывать только назначение« Hello »для s, но операция не должна возвращать никакого значения» - Почему? «Какова причина, позволяющая операторам присваивания возвращать значение?» - Потому что это полезно, как показывают ваши собственные примеры. (Ну, твой второй пример глуп, ноwhile ((s = GetWord()) != null) Process(s); не является).
 Michael Petrotta27 сент. 2010 г., 21:41
Для справки, это поведение определено вC # spec: «Результатом простого выражения присваивания является значение, присвоенное левому операнду. Результат имеет тот же тип, что и левый операнд, и всегда классифицируется как значение».
 jsmith27 сент. 2010 г., 21:48
@Skurmedel Я не downvoter, я согласен с вами. Но, может быть, потому что это считалось риторическим вопросом?
 Andrey27 сент. 2010 г., 21:51
В паскале / delphi присваивание: = ничего не возвращает. Я ненавижу это.

Ответы на вопрос(13)

Вы не предоставили ответ? Это для того, чтобы включить именно те конструкции, которые вы упомянули.

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

string line;
while ((line = streamReader.ReadLine()) != null)
    // ...
 Mike Burton27 сент. 2010 г., 22:40
+1 Это должно быть одним из наиболее практичных применений этой конструкции
 Tom Mayfield28 сент. 2010 г., 00:02
Мне действительно нравится это для действительно простых инициализаторов свойств, но вы должны быть осторожны и провести черту для «простого» минимума для удобочитаемости:return _myBackingField ?? (_myBackingField = CalculateBackingField()); Гораздо меньше болтовни, чем проверка на ноль и присваивание.

Мое любимое использование выражений присваивания для лениво инициализированных свойств.

private string _name;
public string Name
{
    get { return _name ?? (_name = ExpensiveNameGeneratorMethod()); }
}
 configurator10 сент. 2011 г., 21:39
Вот почему так много людей предложили??= синтаксис.

Я думаю, что основной причиной является (преднамеренное) сходство с C ++ и C. Заставить оператор присвоения (и многие другие языковые конструкции) вести себя так же, как их аналоги в C ++, просто следуя принципу наименьшего удивления, и любой программист приходит из другого вьющегося язык скобок может использовать их, не тратя много времени. Быть легко подобранным для программистов на C ++ было одной из главных целей разработки для C #.

Дополнительное преимущество, которое я не вижу в ответах здесь, состоит в том, что синтаксис для назначения основан на арифметике.

Сейчасx = y = b = c = 2 + 3 означает нечто иное в арифметике, чем язык в стиле C; в арифметике это утверждение, мыгосударство что х равен у и т. д. и на языке стиля С это инструкция, котораямарки х равно у и т. д. после его выполнения.

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

 Jon Hanna23 июл. 2015 г., 23:27
@JimBalter Интересно, можно ли через пять лет выступить против.
 Jon Hanna24 июл. 2015 г., 00:04
... неизбежно, потому что этот язык также используется= для сравнения равенства, так чтоa = b = c должно означать, чтоa = b == c значит на языках C-стиля. Я обнаружил, что цепочка разрешений в C намного более интуитивна, потому что я мог провести аналогию с арифметикой.
 Jon Hanna24 июл. 2015 г., 00:01
@JimBalter нет, твой второй комментарий на самом деле является контраргументом и обоснованным. Мое размышление было, потому что ваш первый комментарий не был. Тем не менее, при изучении арифметики мы учимсяa = b = c Значит этоa а такжеb а такжеc это одно и то же. При изучении языка стиля C мы узнаем, что послеa = b = c, a а такжеb это то же самое, чтоc, В моем ответе, конечно, есть разница в семантике, но все же, когда я был ребенком, я впервые научился программировать на языке, который использовал= для назначения, но не позволилa = b = c это казалось мне необоснованным ...

Я думаю, вы неправильно понимаете, как синтаксический анализатор будет интерпретировать этот синтаксис. Задание будет оцененопервый, а затем результат будет сравниваться с NULL, то есть оператор эквивалентен:

s = "Hello"; //s now contains the value "Hello"
(s != null) //returns true.

Как уже отмечали другие, результатом присваивания является присвоенное значение. Мне трудно представить преимущество наличия

((s = "Hello") != null)

а также

s = "Hello";
s != null;

не быть эквивалентным ...

 Dan J27 сент. 2010 г., 22:37
Точка взята, и исправлена!
 Steve Ellinger27 сент. 2010 г., 22:25
Хотя с практической точки зрения вы правы в том, что ваши две строки эквивалентны, ваше утверждение о том, что присвоение не возвращает значение, технически неверно. Насколько я понимаю, результатом присваивания является значение (согласно спецификации C #), и в этом смысле оно ничем не отличается от, например, оператора сложения в выражении типа 1 + 2 + 3
Решение Вопроса

Насколько я понимаю, присваивание s = "Привет"; должен вызывать только s «Hello», но операция не должна возвращать никакого значения.

Ваше понимание на 100% неверно.Можете ли вы объяснить, почему вы верите в эту ложную вещь?

Какова причина, позволяющая операторам присваивания возвращать значение?

Во-первых, назначениезаявления не производите ценность. присваиваниевыражения произвести значение. Выражение присваивания является юридическим утверждением; в C # есть только несколько выражений, которые являются допустимыми утверждениями: ожидающие выражения, выражения конструкции, приращения, декремента, вызова и присваивания могут использоваться там, где ожидается выражение.

В C # есть только один вид выражения, который не производит какого-либо значения, а именно вызов чего-то, что типизируется как возвращающее void. (Или, что эквивалентно, ожидание задачи без ассоциированного значения результата.) Любой другой вид выражения создает значение, либо переменную, либо ссылку, либо доступ к свойству, либо доступ к событию и т. Д.

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

Причина, по которой это возможно, заключается в том, что (1) она часто удобна и (2) она идиоматична в C-подобных языках.

Можно заметить, что вопрос был задан: почему это идиоматично в C-подобных языках?

К сожалению, Деннис Ритчи больше не может спрашивать, но я думаю, что задание почти всегда остается позадизначение, которое было только что присвоено в реестре. С - это язык "очень близко к машине". Кажется правдоподобным, и в соответствии с замыслом C существует языковая функция, которая в основном означает «продолжай использовать значение, которое я только что присвоил». Для этой функции очень легко написать генератор кода; вы просто продолжаете использовать регистр, в котором хранится присвоенное значение.

 Mike Burton27 сент. 2010 г., 22:40
+1 за интересные размышления
 user43729127 сент. 2010 г., 22:56
Не знал, что все, что возвращает значение (даже конструкция экземпляра считается выражением)
 Toby28 июл. 2017 г., 16:51
Я имел в видуправая рука сторона, но ваша точка зрения по-прежнему применима, я согласен, Эрик.
 Eric Lippert29 авг. 2018 г., 20:55
@BobKaufman: забавный факт,JavaScript все еще делает это, Никогда не делай этого, но в JavaScript ты можешь сказатьx=eval("if(whatever)y=2; else z=3;"); а такжеx будет либо2 или же3. eval являетсячистое безудержное зло.
 Eric Lippert28 июл. 2017 г., 16:06
@Toby: Это законно, но я думаю, что это немного пахнет кодом, когда выражение используется как для его эффектов, так и для его значения. Код будет сгенерирован так же, как{ if (f == null) f = new F(); f.p = value;} это всего лишь несколько нажатий клавиш и легче для чтения и отладки.
 toddmo08 янв. 2017 г., 21:25
Может быть, он верил в такое, потому что так работает VB.Net. Спасибо за исправление в C # Эрик.
 Bob Kaufman29 авг. 2018 г., 20:15
В мои 16-битные дни программирования на C, когда подсчитывался каждый байт, я написал код, которыйзависит на этом «продолжать использовать значение, которое я только что назначил» поведение, как вint f() { x = 2 * 3; } намеренно опускаяreturn Заявление о сохранении нескольких байтов этой драгоценной памяти 64 КБ. Можно предположить, хотя и опасно, что результат последнего выражения остался в аккумуляторе, иint функции вернули значение в аккумулятор.
 Timwi28 сент. 2010 г., 00:45
@ user437291: Если конструкция экземпляра не была выражением, вы ничего не могли бы сделать с созданным объектом - вы не могли бы присвоить его чему-либо, вы не могли бы передать его в метод и не могли вызывать какие-либо методы. в теме. Было бы довольно бесполезно, не так ли?
 Jim Balter23 июл. 2015 г., 23:43
«Не знал, что все, что возвращает значение ... считается выражением» - Хм ... все, что возвращает значение, считается выражением - эти два понятия фактически являются синонимами.
 user43729127 сент. 2010 г., 23:15
и спасибо всем за помощь
 mk1225 авг. 2012 г., 00:08
Изменение переменной на самом деле является «просто» побочным эффектом оператора присваивания, который, создавая выражение, дает значение в качестве своей основной цели.
 Toby28 июл. 2017 г., 12:42
Подтвердил для меня, что это нормально делать ленивую инициализацию объектов-членов в C # 6, используя что-то вродеset => (FooObjectField ?? (FooObjectField = new FooObject())).FooObjectProperty = value; (потому что присваивание в левой части оператора null-coalescing возвращает вновь созданный объект).
 Jim Balter23 июл. 2015 г., 23:01
«Ваше понимание на 100% неверно». - В то время как использование ОП английским языком не самое большое, его / ее «понимание» о том, чтодолжен будьте добры, высказывайте свое мнение, так что это не та вещь, которая может быть «на 100% неверной». Некоторые разработчики языка соглашаются с «следует» ОП и делают назначения не имеющими значения или иным образом запрещают им быть подвыражениями. Я думаю, что это чрезмерная реакция на=/== опечатка, которая легко решается путем запрета использования значения= если это не заключено в скобки. например.,if ((x = y)) или жеif ((x = y) == true) разрешено, ноif (x = y) не является.

Помимо уже упомянутых причин (цепочка назначений, настройка и проверка в циклах while и т. Д.),должным образом использоватьusing Скажите, вам нужна эта функция:

using (Font font3 = new Font("Arial", 10.0f))
{
    // Use font3.
}

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

 kenny27 сент. 2010 г., 22:53
Я не думаю, что это действительно цепочка заданий как таковая.
 kenny28 сент. 2010 г., 00:31
Но, как заметил Эрик выше, «операторы присваивания не возвращают значение». На самом деле это просто языковой синтаксис оператора using, доказательством тому является то, что нельзя использовать «выражение присваивания» в операторе using.
 Martin Törnwall28 сент. 2010 г., 07:29
@kenny: Извинения, моя терминология была явно неправильной. Я понимаю, что оператор using мог бы быть реализован без поддержки языка выражений присваивания, возвращающих значение. Это было бы, на мой взгляд, ужасно непоследовательным.
 Martin Törnwall27 сент. 2010 г., 23:32
@kenny: Э-э ... Нет, но я никогда не утверждал, что это так. Как я сказал,в сторону Исходя из упомянутых выше причин, включая цепочку присваивания, оператор using будет намного сложнее использовать, если бы не тот факт, что оператор присваивания возвращает результат операции присваивания. Это на самом деле не связано с цепочкой назначений вообще.
 configurator10 сент. 2011 г., 21:37
@kenny: На самом деле, можно использовать оператор определения и назначения,или же любое выражение вusing, Все это законно:using (X x = new X()), using (x = new X()), using (x), Однако в этом примере содержимое оператора using представляет собой специальный синтаксис, который совсем не зависит от присваивания, возвращающего значение -Font font3 = new Font("Arial", 10.0f) не является выражением и недопустимо в любом месте, которое ожидает выражения.
 Jim Balter23 июл. 2015 г., 23:18
«чтобы правильно использовать оператор использования, вам нужна эта функция» - нет, скорее всего, нет. «без языка, имеющего общую поддержку выражений присваивания, возвращающих значение» - почему вы говорите о выражениях присваивания? Это определение переменной с инициализатором, которое не является выражением. «Это было бы, на мой взгляд, ужасно непоследовательным». - Ваш примерusing (definition) полностью противоречит тебе ... больше нигде такая конструкция не может быть использована.

По двум причинам, которые вы включаете в свой пост
1) так что вы можете сделатьa = b = c = 16
2) чтобы вы могли проверить, успешно ли выполнено заданиеif ((s = openSomeHandle()) != null)

С одной стороны, это позволяет вам связывать свои назначения, как в вашем примере:

a = b = c = 16;

Для другого, это позволяет вам присваивать и проверять результат в одном выражении:

while ((s = foo.getSomeString()) != null) { /* ... */ }

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

 Mike Burton27 сент. 2010 г., 22:41
+1 Цепочка не является критической функцией, но приятно видеть, что она «просто работает», когда так много вещей не работает.
 Caleb Bell30 нояб. 2011 г., 18:59
+1 для цепочки также; Я всегда думал об этом как о особом случае, который обрабатывался языком, а не как естественный побочный эффект «выражений, возвращающих значения».
 Serge Shultz12 мар. 2013 г., 10:48
Это также позволяет вам использовать присваивание в возвращениях:return (HttpContext.Current.Items["x"] = myvar);

Если присвоение не вернуло значение, строкаa = b = c = 16 тоже не сработает.

Также возможность писать такие вещи, какwhile ((s = readLine()) != null) может быть полезным иногда.

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

 Jon11 мар. 2014 г., 16:35
Никто не упомянул о недостатках - я пришел сюда, задаваясь вопросом, почему назначения не могут возвращать ноль, чтобы общая ошибка сравнения с назначением «если (что-то = 1) {}» не компилировалась, а не всегда возвращала true. Теперь я вижу, что это может создать ... другие проблемы!
 Jim Balter23 июл. 2015 г., 23:10
@ Джон, что эту ошибку можно легко предотвратить, запретив или предупредив о= встречается в выражении, которое не заключено в скобки (не считая скобок самого оператора if / while). gcc выдает такие предупреждения и тем самым существенно устраняет этот класс ошибок в программах на C / C ++, которые скомпилированы с ним. Жаль, что другие авторы компиляторов так мало внимания уделяли этому и нескольким другим хорошим идеям в gcc.

Еще один отличный пример использования, я использую это все время:

var x = _myVariable ?? (_myVariable = GetVariable());
//for example: when used inside a loop, "GetVariable" will be called only once

Я хотел бы остановиться на конкретном моменте, который Эрик Липперт сделал в своем ответе, и уделить особое внимание конкретному случаю, который никто еще не затронул. Эрик сказал:

[...] присваивание почти всегда оставляет значение, только что назначенное в регистре.

Я хотел бы сказать, что присвоение всегда будет оставлять позади значение, которое мы пытались присвоить нашему левому операнду. Не просто "почти всегда". Но я не знаю, потому что я не нашел эту проблему в документации. Теоретически это может быть очень эффективная реализованная процедура, чтобы «оставить позади», а не переоценивать левый операнд, но эффективна ли она?

«Эффективно» да для всех примеров, построенных до сих пор в ответах этой ветки. Но эффективны ли в случае свойств и индексаторов, которые используют методы доступа get и set? Не за что. Рассмотрим этот код:

class Test
{
    public bool MyProperty { get { return true; } set { ; } }
}

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

Test test = new Test();

if ((test.MyProperty = false) == true)
    Console.WriteLine("Please print this text.");

else
    Console.WriteLine("Unexpected!!");

Угадайте, что это печатает? Это печатаетUnexpected!!, Как выясняется, метод доступа set действительно вызывается, что ничего не делает. Но после этого метод доступа get никогда не вызывается вообще. Назначение просто оставляет позадиfalse Значение, которое мы пытались присвоить нашей собственности. И этоfalse значение - это то, что вычисляет оператор if.

Я закончу с примером из реального мира это заставило меня исследовать эту проблему. Я сделал индексатор, который был удобной оберткой для коллекции (List<string>) что мой класс имел в качестве частной переменной.

Параметр, отправленный индексатору, был строкой, которую нужно было рассматривать как значение в моей коллекции. Метод доступа get просто возвращает true или false, если это значение существует в списке или нет. Таким образом, метод доступа get был еще одним способом использованияList<T>.Contains метод.

Если метод доступа индексатора был вызван со строкой в ​​качестве аргумента и правильный операнд был booltrueон добавил бы этот параметр в список. Но если тот же параметр был отправлен в метод доступа, а правым операндом был булfalseвместо этого он удалил бы элемент из списка. Таким образом, доступный набор использовался как удобная альтернатива обоимList<T>.Add а такжеList<T>.Remove.

Я думал, что у меня был аккуратный и компактный «API», оборачивающий список своей собственной логикой, реализованной как шлюз. С помощью только одного индексатора я мог сделать много вещей с помощью нескольких нажатий клавиш. Например, как я могу попытаться добавить значение в мой список и убедиться, что оно там? Я думал, что это была единственная необходимая строка кода:

if (myObject["stringValue"] = true)
    ; // Set operation succeeded..!

Но, как показал мой предыдущий пример, метод доступа get, который должен видеть, действительно ли значение находится в списке, даже не вызывался.true Значение всегда оставалось позади, эффективно разрушая любую логику, которую я реализовал в своем методе доступа get.

 Elliot12 февр. 2015 г., 16:04
Очень интересный ответ. И это заставило меня положительно подумать о разработке, управляемой тестами.
 Davos01 сент. 2016 г., 13:42
Я не ожидал бы, что попытка выражения присваивания сначала получит значение целевого свойства. Если переменная изменчива, а типы совпадают, почему это имеет значение, какое было старое значение? Просто установите новое значение. Я не фанат заданий в тестах IF, хотя. Возвращается ли значение true, потому что назначение было успешным, или это какое-то значение, например положительное целое число, приведенное к истине, или потому, что вы присваиваете логическое значение? Независимо от реализации, логика неоднозначна.
 Jim Balter23 июл. 2015 г., 23:25
Хотя это интересно, это комментарий к ответу Эрика, а не ответ на вопрос ОП.

Тот факт, что «a ++» или «printf (« foo »)» может быть полезен либо как самостоятельное утверждение, либо как часть более крупного выражения, означает, что C должен учитывать возможность того, что результаты выражения могут быть или не быть используемый. Учитывая это, существует общее понятие, что выражения, которые могут с пользой «возвращать» значение, могут также делать это. Цепочка назначений может быть немного «интересной» в C и даже более интересной в C ++, если все рассматриваемые переменные не имеют точно одинаковый тип. Такое использование, вероятно, лучше избегать.

Ваш ответ на вопрос