C#自增ToString

5
我发现C#/WPF中出现了意外的行为。
    private void ButtonUp_Click(object sender, RoutedEventArgs e)
    {
        int quant;
        if( int.TryParse(Qnt.Text, out quant))
        {
            string s = ((quant++).ToString());
            Qnt.Text = s;
        }
    }

如果我将quant设为1,那么quant将增加到2。但是s字符串将是1。这是优先级的问题吗?

编辑:

我重写了这句话:

            quant++;
            Qnt.Text = quant.ToString();

现在这个功能按照我预期的工作。


请尝试阅读以下内容:https://dev59.com/tXA75IYBdhLWcg3wVniI#3346729 - xanatos
非常抱歉问这样基础的问题。这是C和C++的基础知识。 - sergiol
引用链接中的话:“此外,C#的做法与C的做法不同”,因此没有任何问题。 - xanatos
在我的C/C++学术时期,我做了无数像这样的语句: printf("i: %d",i++); 所以,真的很对不起。 - sergiol
5个回答

6
问题在于您使用的是后增,而不是前增...但是为什么要写这种复杂的代码呢?只需分离出副作用(增量)和ToString调用即可:
if (int.TryParse(Qnt.Text, out quant))
{
    quant++;
    Qnt.Text = quant.ToString();
}

如果您不打算再次读取值,甚至可以放弃实际的增量

if (int.TryParse(Qnt.Text, out quant))
{
    Qnt.Text = (quant + 1).ToString();
}

尽可能地,在其他表达式中避免使用复合赋值。这通常会导致问题。

此外,感觉所有这些解析和格式化都隐藏了真正的模型,即某个地方应该有一个int属性,它可能会在UI中反映出来。例如:

private void ButtonUp_Click(object sender, RoutedEventArgs e)
{
    // This is an int property
    Quantity++;
    // Now reflect the change in the UI. Ideally, do this through binding
    // instead.
    Qnt.Text = Quantity.ToString();
}

+1 因为你是唯一一个没有陷入尝试解释前后增量的陷阱的人 :-) :-) - xanatos
+1 是因为你提出了正确的方法:触摸属性值而不是 UI 上的表示。由于这是在 WPF 应用程序中,这个方法更加合理。 - sergiol

6
您正在使用后置递增操作符。它会先返回原始值,然后再加1。如果您想要一行代码实现所需功能,可以使用前置递增操作符代替。
(++quant).ToString();

但更好的方法是避免所有这些陷阱,像这样做:
quant++;
string s = quant.ToString();

第一版本需要考虑事情发生的顺序,而第二版本则不需要考虑。始终将代码清晰度视为比简洁更重要的因素。

很容易相信单行版本在某种程度上更快,但这是不正确的。在1970年代的C系统中可能是正确的,但即使那时我也怀疑。


1
+1 因为你的回复是唯一一个正确解释前/后增量并且使用了正确术语的。 :-) - xanatos

1

现在我要做一些不该做的事情……我将尝试简化 Eric Lippert 在这里写的内容 What is the difference between i++ and ++i? 我希望我没有写错什么 :-)

现在…… 前缀递增和后缀递增运算符是做什么的?简化并忽略它们之间的所有副本(并记住它们不是多线程环境中的原子运算符):

它们都是表达式(如i + 1),返回结果(如i + 1),但具有副作用(不像i + 1)。副作用是它们递增变量i。重要问题是“所有的事情发生的顺序是什么?”答案非常简单:

  • 前置自增 ++i:将i加1并返回i新值
  • 后置自增 i++:将i加1并返回i旧值

现在...重要的是,增量i总是先发生。然后返回一个值(旧值或新值)。

让我们举个例子(Lippert的例子相当复杂。我会举一个不太完整但足以检查我之前说的顺序是否正确的不同、更简单的例子)(技术上我会举两个例子)

例子1:

unchecked
{
    int i = Int32.MaxValue;
    Console.WriteLine("Hello! I'm trying to do my work here {0}", i++);
    Console.WriteLine("Work done {1}", i);
}

例子2:

checked
{
    int i = Int32.MaxValue;
    Console.WriteLine("Hello! I'm trying to do my work here {0}", i++);
    Console.WriteLine("Work done {1}", i);
}

checked 表示如果溢出会抛出异常 (OverflowException)。 unchecked 表示相同的操作不会抛出异常。 Int32.MaxValue + 1 肯定会溢出。使用 checked 会抛出异常,而使用 unchecked i 将变为 -1。

让我们尝试运行第一段代码。结果:

Hello! I'm trying to do my work here 2147483647
Work done -1

好的... i 已经被增加了,但是 Console.WriteLine 接收到了旧值 (Int32.MaxValue == 2147483647)。从这个例子中我们无法确定后缀自增运算符和调用 Console.WriteLine 的顺序。

让我们尝试运行第二段代码。结果:

System.OverflowException: Arithmetic operation resulted in an overflow.

好的...很明显首先执行了后增量,导致异常,然后显然Console.WriteLine没有被执行(因为程序结束了)。

所以我们知道我说的顺序是正确的。

现在。你应该从这个例子中学到什么?和我多年前学到的一样。在C和C#中,前缀和后缀增量适用于混淆代码比赛。它们不适用于许多其他事情(但请注意,C++是不同的!)。从那个教训中,我学到了只有两个地方可以自由使用后增量,而没有一个地方可以自由使用前增量。

“安全”的后增量

for (int i = 0; i < x; i++)

并且

i++; // Written alone. Nothing else on the same line but a comment if necessary.

“安全”的前缀递增

(nothing)

-1
在这种情况下,首先会调用quant.ToString(),然后增加quant的值。
如果你写((++quant).ToString()),第一步将是增加quant的值,然后调用quant.ToString()

1
错误的,阅读 https://dev59.com/tXA75IYBdhLWcg3wVniI#3346729 在调用方法之前增量已经完成(并保存在原始变量中),但是方法仍然接收到“旧”的值。 - xanatos
正如Lipper所说,这是一个常见的错误,很多书籍(可悲的是)没有指出。 - xanatos
1
没错。我学习Java时也是这样教的(但我猜其他编程语言应该也差不多),实际解释就像我在原回答中所说的那样。这可能就像高中物理课一样:他们教你的确实有助于你理解事物的运作方式,但并不能告诉你它们是如何真正工作的。 - Pieter

-1

string s = ((quant++).ToString());

可以翻译为:

可以这样分步执行:

在递增之前使用 quant 调用 toString() 方法,然后

执行赋值运算符,接着

递增 `quant'

可以尝试使用 ++quant。


1
错误的,阅读 https://dev59.com/tXA75IYBdhLWcg3wVniI#3346729 在调用方法之前增量已经完成(并保存在原始变量中),但是方法仍然接收到“旧”的值。 - xanatos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接