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

时间:2010-09-27 19:36:58

标签: c# syntax

这是允许的:

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

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

据我了解,作业s = ”Hello”;只应将“Hello”分配给s,但操作不应返回任何值。如果这是真的,那么((s = "Hello") != null)会产生错误,因为null将被比较为空。

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

14 个答案:

答案 0 :(得分:149)

  

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

您的理解是100%不正确的。 你能解释为什么你相信这个错误的东西吗?

  

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

首先,分配语句不会产生值。赋值表达式生成一个值。赋值表达式是一种法律陈述;只有少数表达式是C#中的合法语句:等待表达式,实例构造,递增,递减,调用和赋值表达式可用于需要语句的地方。

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

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

允许此功能背后的原因是因为(1)它通常很方便,(2)它在C语言中是惯用的。

有人可能会注意到这个问题已被乞求:为什么这种习惯用C语言?

不幸的是,Dennis Ritchie不再可以问,但我的猜测是,一项任务几乎总是留下在寄存器中分配的值。 C是一种非常“接近机器”的语言。这似乎是合理的,并且与C的设计保持一致,语言特征基本上意味着“继续使用我刚刚分配的值”。为此功能编写代码生成器非常容易;你只需继续使用存储已分配值的寄存器。

答案 1 :(得分:43)

你没有提供答案吗?这是为了准确启用你提到的各种结构。

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

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

答案 2 :(得分:30)

我最喜欢使用赋值表达式是为了延迟初始化属性。

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

答案 3 :(得分:27)

首先,它允许您链接作业,如您的示例所示:

a = b = c = 16;

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

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

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

答案 4 :(得分:14)

除了已经提到的原因(分配链接,while循环中的设置和测试等),要properly使用using语句,您需要此功能:

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

MSDN不鼓励在using语句之外声明一次性对象,因为它甚至在处理后仍保留在范围内(参见我链接的MSDN文章)。

答案 5 :(得分:8)

我想详细说明Eric Lippert在他的回答中提出的具体观点,并把焦点放在一个其他人都没有触及的特定场合。埃里克说:

  

[...]任务几乎总是留下刚刚在寄存器中分配的值。

我想说,赋值总是会留下我们试图赋予左操作数的值。不只是“几乎总是”。但我不知道,因为我没有在文档中发现此问题。从理论上讲,它可能是一个非常有效的实施程序,“留下”而不是重新评估左操作数,但它是否有效?

对于此线程的答案中构建的所有示例,

'Efficient'是。但是在使用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方法的方法。

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

我认为我有一个整洁而紧凑的“API”包装列表,我自己的逻辑实现为网关。在索引器的帮助下,我可以用几组击键做很多事情。例如,我如何尝试将值添加到列表中并验证它在那里?我认为这是唯一必要的代码行:

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

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

答案 6 :(得分:6)

如果赋值未返回值,则行a = b = c = 16也不起作用。

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

因此让分配返回指定值的原因是让你做那些事情。

答案 7 :(得分:4)

我认为你误解了解析器将如何解释这种语法。将对 first 进行分配,然后将结果与NULL进行比较,即该语句等效于:

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

正如其他人所指出的,分配的结果是指定的值。我发现很难想象拥有

的优势

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

s = "Hello";
s != null;

等同于......

答案 8 :(得分:3)

我认为主要原因是(有意)与C ++和C的相似性。使得分配运算符(以及许多其他语言结构)的行为与它们的C ++对应物一样,只是遵循最少惊喜的原则,并且任何程序员来自另一种花括号语言可以使用它们而不需要花太多时间考虑。容易接受C ++程序员是C#的主要设计目标之一。

答案 9 :(得分:2)

由于您在帖子中包含的两个原因
1)所以你可以做a = b = c = 16
2)这样你就可以测试作业是否成功 if ((s = openSomeHandle()) != null)

答案 10 :(得分:1)

'a ++'或'printf(“foo”)'可能作为自包含语句或作为更大表达式的一部分有用的事实意味着C必须允许表达式结果可能或可能不会被使用。鉴于此,有一个普遍的概念,即可能有用地“返回”一个值的表达式也可以这样做。如果所讨论的所有变量都没有完全相同的类型,则C中的赋值链可能略微“有趣”,而在C ++中则更有趣。这种用法可能是最好的避免。

答案 11 :(得分:0)

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

现在x = y = b = c = 2 + 3意味着算术与C风格的语言不同;在算术中它是一个断言,我们陈述 x等于y等等。在C风格的语言中,它是使 x等于y等等的指令被执行。

这就是说,算法和代码之间仍然有足够的关系,除非有充分的理由否则算术中不允许什么是自然的。 (C风格语言使用等号符号的另一件事是使用==进行相等比较。虽然因为最右边的==返回一个值,这种链接是不可能的。)

答案 12 :(得分:0)

另一个很好的用例示例,我一直使用它:

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

答案 13 :(得分:0)

当需要更新大量内容并返回是否有任何更改时,我喜欢使用赋值返回值:

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");

但是,这将在找到第一个为真之后停止对or语句的求值。在这种情况下,这意味着一旦分配了第一个不同的值,它将停止分配后续值。

请参见https://dotnetfiddle.net/e05Rh8来试用