赋值语句为什么返回一个值?

这是允许的:

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


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

根据我的理解,赋值 s = ”Hello”;应该只会导致将 “Hello”赋值给 s,但是操作不应该返回任何值。如果这是真的,那么 ((s = "Hello") != null)将产生一个错误,因为 null将被比较为无。

允许赋值语句返回值背后的原因是什么?

30180 次浏览

首先,它允许你链接你的任务,就像你的例子:

a = b = c = 16;

另一方面,它允许您在单个表达式中分配和检查结果:

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

两者都可能是可疑的原因,但肯定有人喜欢这些构造。

难道你没有提供答案吗? 那就是启用你刚才提到的那种结构。

使用赋值运算符的此属性的一种常见情况是从文件中读取行..。

string line;
while ((line = streamReader.ReadLine()) != null)
// ...

如果赋值不返回值,那么行 a = b = c = 16也不会工作。

有时候,能够写出像 while ((s = readLine()) != null)这样的东西也是很有用的。

所以让赋值返回赋值的原因就是让你做这些事情。

原因有二,你在你的文章中包括
1)所以你可以做 a = b = c = 16
2)这样你就可以测试作业是否成功 if ((s = openSomeHandle()) != null)

我认为您误解了解析器将如何解释该语法。赋值将被求值为 第一,然后将结果与 NULL 进行比较,即语句等价于:

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

正如其他人指出的,赋值的结果就是赋值。我很难想象

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

还有

s = "Hello";
s != null;

不等同于..。

我认为主要的原因是与 C + + 和 C 的(有意的)相似性。让赋值操作符(以及许多其他语言结构)表现得像它们的 C + + 操作符一样,只是遵循最不出人意料的原则,任何来自另一种大括号语言的程序员都可以毫不费力地使用它们。对于 C + + 程序员来说,容易理解是 C # 的主要设计目标之一。

除了上面提到的原因(赋值链、 while 循环中的 set-and-test 等) ,对于 适当地来说,使用 using语句还需要以下特性:

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

MSDN 不鼓励在 using 语句之外声明一次性对象,因为即使在它被释放之后,它仍将保留在作用域中(请参阅 MSDN 文章 I 链接)。

根据我的理解,赋值 s = “ Hello”; 应该只会将“ Hello”赋给 s,但是操作不应该返回任何值。

你的理解是100% 错误的

允许赋值语句返回值背后的原因是什么?

首先,赋值 声明不产生值。作业 表情产生一个值。赋值表达式是一个法律语句; 在 C # 中只有少数几个表达式是法律语句: 等待一个表达式,实例构造,增量,减量,调用和赋值表达式可以用在需要一个语句的地方。

在 C # 中,只有一种表达式不产生某种值,即对返回 void 类型的事物的调用。(或者,相当于没有相关结果值的任务等待。)每种其他类型的表达式都会产生一个值或变量、引用、属性访问或事件访问等。

注意,所有作为语句合法的表达式都是 对副作用有用。这是关键的洞察力,我认为也许是你的直觉的原因,作业应该是陈述,而不是表达。理想情况下,每个语句只有一个副作用,表达式中没有副作用。在表达式上下文中使用副作用代码有点奇怪。

允许这个特性背后的原因是: (1)它经常是方便的; (2)它在类 C 语言中是惯用的。

人们可能会注意到,有人提出了这样一个问题: 为什么这种习惯用法在类 C 语言中出现?

不幸的是,丹尼斯 · 里奇已经不能提问了,但是我猜测作业几乎总是在寄存器中留下 刚刚分配的值。C 是一种非常“接近机器”的语言。这似乎是合理的,并与 C 的设计保持一致,有一个语言功能,基本上意味着“继续使用我刚刚分配的值”。为这个特性编写代码生成器非常容易; 您只需继续使用存储所赋值的寄存器。

“ a + +”或“ printf (“ foo”)”可能作为一个自包含的语句或作为一个较大表达式的一部分有用,这一事实意味着 C 必须考虑到表达式结果可能会被使用或可能不会被使用的可能性。有鉴于此,有一个普遍的概念,表达式可能有用的“返回”一个值,也可以这样做。赋值链在 C 语言中可能有点“有趣”,在 C + + 中甚至更有趣,如果所讨论的所有变量不具有完全相同的类型。最好避免这种用法。

赋值表达式我最喜欢的用法是用于延迟初始化的属性。

private string _name;
public string Name
{
get { return _name ?? (_name = ExpensiveNameGeneratorMethod()); }
}

我在这里的答案中没有看到的一个额外的优势是赋值语法是基于算术的。

现在,x = y = b = c = 2 + 3在算术语言中的意思和 C 风格的语言不同; 在算术语言中,它是一个断言,我们 国家中 x 等于 y 等,在 C 风格的语言中,它是在执行后 制造中 x 等于 y 等的指令。

也就是说,算法和代码之间仍然有足够的联系,除非有充分的理由,否则不允许算法中的自然规律是没有意义的。(C 样式语言从使用等于符号中获得的另一件事是使用 = = 进行等式比较。但是在这里,因为 right-most = = 返回一个值,所以这种链接是不可能的。)

我想详细阐述一下埃里克 · 利伯特在他的回答中提出的一个具体观点,并把聚光灯放在一个其他任何人都没有提到过的特定场合。埃里克表示:

[ ... ]赋值几乎总是留下刚刚在寄存器中赋的值。

我想说赋值总是会留下我们试图赋给左操作数的值。不只是“几乎总是”。但是我不知道,因为我还没有在文档中发现这个问题的注释。理论上,“留下”并且不重新计算左操作数可能是一个非常有效的实现过程,但是它有效吗?

到目前为止,在这个线程的答案中构建的所有示例都是“高效”的。但是在使用 get-and 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 语句计算的值。

我将以一个真实世界的例子 作为结束,这个例子让我开始研究这个问题。我创建了一个 indexer,它是一个集合(List<string>)的方便包装器,我的一个类把它作为一个私有变量。

发送给索引器的参数是一个字符串,该字符串将被视为集合中的一个值。如果列表中存在该值,get 访问器只返回 true 或 false。因此 get 访问器是使用 List<T>.Contains方法的另一种方法。

如果使用字符串作为参数调用索引器的 set 访问器,并且右边的操作数是 bool true,那么他会将该参数添加到列表中。但是,如果将相同的参数发送给访问器,并且正确的操作数是 bool false,那么他将从列表中删除该元素。因此,集访问器被用作方便的 List<T>.AddList<T>.Remove的替代品。

我认为我有一个整洁和紧凑的“ API”包装列表与我自己的逻辑实现为一个网关。在一个索引器的帮助下,我可以做很多事情,只需要一些击键。例如,我如何尝试向列表中添加一个值并验证它是否在其中?我认为这是唯一需要的代码行:

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

但是,正如我前面的示例所示,get 访问器本应查看列表中的值是否真的存在,但它甚至没有被调用。true值总是被留下来,有效地破坏了我在 get 访问器中实现的任何逻辑。

另一个很好的例子用例,我一直在使用:

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

我喜欢在需要更新一大堆内容时使用赋值返回值,无论是否有任何更改,我都会返回:

bool hasChanged = false;


hasChanged |= thing.Property != (thing.Property = "Value");
hasChanged |= thing.AnotherProperty != (thing.AnotherProperty = 42);
hasChanged |= thing.OneMore != (thing.OneMore = "They get it");


return hasChanged;

不过要小心。你可能认为你可以把它缩短为:

return thing.Property != (thing.Property = "Value") ||
thing.AnotherProperty != (thing.AnotherProperty = 42) ||
thing.OneMore != (thing.OneMore = "They get it");

但是这实际上会在找到第一个 true 之后停止计算 or 语句。在这种情况下,这意味着一旦它分配了第一个不同的值,它就停止分配后续的值。

请参阅 https://dotnetfiddle.net/e05Rh8以了解此内容