为什么这不会导致事件的无限循环呢?

时间:2022-03-25 15:51:34

I have a simple application that reverses any text typed to it in another textbox. The catch is, you can modify either textbox and the changes will be (literally) reflected in the other.

我有一个简单的应用程序,可以将在另一个文本框中键入的任何文本进行反向转换。关键是,您可以修改任何一个文本框,而修改将(字面上)反映在另一个文本框中。

I wrote this code, believing for it to cause problems.

我编写了这个代码,相信它会引起问题。

private void realText_TextChanged(object sender, EventArgs e)
{
    mirrorText.Text = mirror(realText.Text);
}

private void mirrorText_TextChanged(object sender, EventArgs e)
{
    realText.Text = mirror(mirrorText.Text);
}

private string mirror(string text)
{
    return new string(text.Reverse().ToArray()).Replace("\n\r", "\r\n");
}

I then tried it out, believing that it would cause an infinite loop (realText changes mirrorText, another event happens, mirrorText changes realText, etc). However, nothing except the intended behavior happened.

然后我尝试了一下,相信它会导致一个无限循环(realText更改mirrorText,另一个事件发生,mirrorText更改realText,等等)。然而,除了预期的行为之外,什么也没有发生。

I'm of course happy about this, I could just leave it here. Or could I?

我当然很高兴,我可以把它留在这里。或者我可以吗?

I'm quite sure the TextChanged event is supposed to be fired whenever Text is changed. Is this intended behavior of some error protection in the events, or was I just lucky? Can this code misbehave on another computer, with other build settings, etc? It can be easily fixed:

我很确定TextChanged事件应该在文本发生更改时触发。这是事件中错误保护的意图行为,还是我很幸运?这段代码是否会在另一台计算机上、其他构建设置上出现错误?它很容易修复:

private void realText_TextChanged(object sender, EventArgs e)
{
    if (realText.Focused)
    {
        mirrorText.Text = Mirror(realText.Text);
    }
}

I'll probably do it anyway to be safe, but is it required to check this? (I'm not even going to ask if it's recommended.)

为了安全起见,我可能会这么做,但这需要检查吗?(我甚至都不去问它是否被推荐。)

4 个解决方案

#1


30  

Per the comments, and as already answered, the TextChanged event is not getting raised when you set the Text property to the value it already has.

根据注释,正如已经回答的那样,当您将Text属性设置为它已经拥有的值时,不会引发TextChanged事件。

It's not clear whether this is something you can safely rely upon. It is a sensible optimisation, and I would be very surprised if future versions of .NET Framework drop it, but I cannot speak for older versions, nor for third-party implementations (Mono).

目前还不清楚这是否是你可以放心信赖的东西。这是一种明智的优化,如果将来的。net Framework版本放弃它,我会感到非常惊讶,但我不能代表旧版本,也不能代表第三方实现(Mono)。

To be absolutely safe, I would not use the Focused check you put in your question. I would do exactly what the Text setter does now.

为了绝对安全,我不会使用你提出的问题的重点检查。我要做的和文本setter一样。

private void realText_TextChanged(object sender, EventArgs e)
{
    var newMirrorText = Mirror(realText.Text);
    if (mirrorText.Text != newMirrorText)
        mirrorText.Text = newMirrorText;
}

This has the same advantage of preventing infinite recursion, but plays more nicely with other code you may put in your form that changes the text as a result of some other event.

这与防止无限递归具有相同的优点,但是与其他代码相比,它可以更好地发挥作用,您可以将这些代码放在窗体中,从而更改其他事件的文本。

#2


21  

The reason it doesn't cause a loop is that it checks whether the Text property actually changed, i.e. if the new value does not equal the old value. In your case the mirror function happens to reverse itself, which leads to the same text after two passes.

它不引起循环的原因是它检查文本属性是否真的改变了,例如,如果新值不等于旧值。在您的例子中,镜像函数恰好反转了自身,这导致经过两次之后出现相同的文本。

#3


3  

It's pretty easy to check.

很容易检查。

First, replace both textbox controls with

首先,将两个文本框控件替换为

    class T : TextBox
    {
        public override string Text
        {
            get
            {
                return base.Text;
            }
            set
            {
                base.Text = value;
            }
        }
    }

Second, set the breakpoint on setter. Add these expressions to the Watch window:

第二,在setter上设置断点。将这些表达式添加到手表窗口:

  • Name
  • 的名字
  • Text
  • 文本
  • value
  • 价值

Third, launch the app, copy '123' from somewhere and paste it to the first textbox. Here it goes:

第三,启动应用程序,从某处复制“123”并粘贴到第一个文本框。这里是:

1st break:

突破1:

  • Name: "mirrorText"
  • 名称:“mirrorText”
  • Text: ""
  • 文字:“
  • value: "321"
  • 价值:“321”

2nd break:

突破2:

  • Name: "realText"
  • 名称:“realText”
  • Text: "123"
  • 文字:“123”
  • value: "123"
  • 价值:“123”

3rd... whoops, it does not breaks anymore. To detect why we had to go deeper. Look at referencesource: text box setter does nothing unusual, but TextBoxBase's one looks interesting:

第三……哎呀,它再也不断裂了。为了发现我们为什么要深入研究。看看referencesource:文本框setter没什么特别的,但是TextBoxBase的这个看起来很有趣:

        set {
            if (value != base.Text) { // Gotcha!
                base.Text = value;
                if (IsHandleCreated) {
                    // clear the modified flag
                    SendMessage(NativeMethods.EM_SETMODIFY, 0, 0);
                }
            }
        }

So, as hvd already answered, the reason is the textbox does not raise TextChanged if old and new values are the same. I don't think the behavior will change, at least for winforms. But if you want more robust solution, here it is:

因此,正如hvd已经回答的,原因是如果旧值和新值相同,文本框不会引发TextChanged。我认为这种行为不会改变,至少对winforms来说是这样。但如果你想要更稳健的解决方案,这里是:

    private void RunOnce(ref bool flag, Action callback)
    {
        if (!flag)
        {
            try
            {
                flag = true;
                callback();
            }
            finally
            {
                flag = false;
            }
        }
    }

    private bool inMirror;
    private void realText_TextChanged(object sender, EventArgs e)
    {
        RunOnce(ref inMirror, () =>
        {
            mirrorText.Text = mirror(realText.Text);
        });
    }

    private void mirrorText_TextChanged(object sender, EventArgs e)
    {
        RunOnce(ref inMirror, () =>
        {
            realText.Text = mirror(mirrorText.Text);
        });
    }

    private string mirror(string text)
    {
        return new string(text.Reverse().ToArray()).Replace("\n\r", "\r\n");
    }

P.S. mirror() will fail on surrogate pairs. Here're some solutions.

在代理对上,P.S. mirror()将失败。下面是一些解决方案。

#4


-1  

If textbox has a Text, and we try to change it with the same Text, the TextChange event is not raising because new text is same as the previous. In your code, the realText_TextChanged event reverses the text and changes the mirrorText with it. The mirrorText_TextChanged event reverses the text and try to change the realText. The realText has already this text and does not raises the realText_TextChanged event.

如果文本框中有一个文本,我们试图用相同的文本修改它,那么TextChange事件不会发生,因为新文本与之前的文本相同。在您的代码中,realText_TextChanged事件反转文本并使用它更改mirrorText。mirrorText_TextChanged事件反转文本并尝试更改realText。realText已经有此文本,不会引发realText_TextChanged事件。

#1


30  

Per the comments, and as already answered, the TextChanged event is not getting raised when you set the Text property to the value it already has.

根据注释,正如已经回答的那样,当您将Text属性设置为它已经拥有的值时,不会引发TextChanged事件。

It's not clear whether this is something you can safely rely upon. It is a sensible optimisation, and I would be very surprised if future versions of .NET Framework drop it, but I cannot speak for older versions, nor for third-party implementations (Mono).

目前还不清楚这是否是你可以放心信赖的东西。这是一种明智的优化,如果将来的。net Framework版本放弃它,我会感到非常惊讶,但我不能代表旧版本,也不能代表第三方实现(Mono)。

To be absolutely safe, I would not use the Focused check you put in your question. I would do exactly what the Text setter does now.

为了绝对安全,我不会使用你提出的问题的重点检查。我要做的和文本setter一样。

private void realText_TextChanged(object sender, EventArgs e)
{
    var newMirrorText = Mirror(realText.Text);
    if (mirrorText.Text != newMirrorText)
        mirrorText.Text = newMirrorText;
}

This has the same advantage of preventing infinite recursion, but plays more nicely with other code you may put in your form that changes the text as a result of some other event.

这与防止无限递归具有相同的优点,但是与其他代码相比,它可以更好地发挥作用,您可以将这些代码放在窗体中,从而更改其他事件的文本。

#2


21  

The reason it doesn't cause a loop is that it checks whether the Text property actually changed, i.e. if the new value does not equal the old value. In your case the mirror function happens to reverse itself, which leads to the same text after two passes.

它不引起循环的原因是它检查文本属性是否真的改变了,例如,如果新值不等于旧值。在您的例子中,镜像函数恰好反转了自身,这导致经过两次之后出现相同的文本。

#3


3  

It's pretty easy to check.

很容易检查。

First, replace both textbox controls with

首先,将两个文本框控件替换为

    class T : TextBox
    {
        public override string Text
        {
            get
            {
                return base.Text;
            }
            set
            {
                base.Text = value;
            }
        }
    }

Second, set the breakpoint on setter. Add these expressions to the Watch window:

第二,在setter上设置断点。将这些表达式添加到手表窗口:

  • Name
  • 的名字
  • Text
  • 文本
  • value
  • 价值

Third, launch the app, copy '123' from somewhere and paste it to the first textbox. Here it goes:

第三,启动应用程序,从某处复制“123”并粘贴到第一个文本框。这里是:

1st break:

突破1:

  • Name: "mirrorText"
  • 名称:“mirrorText”
  • Text: ""
  • 文字:“
  • value: "321"
  • 价值:“321”

2nd break:

突破2:

  • Name: "realText"
  • 名称:“realText”
  • Text: "123"
  • 文字:“123”
  • value: "123"
  • 价值:“123”

3rd... whoops, it does not breaks anymore. To detect why we had to go deeper. Look at referencesource: text box setter does nothing unusual, but TextBoxBase's one looks interesting:

第三……哎呀,它再也不断裂了。为了发现我们为什么要深入研究。看看referencesource:文本框setter没什么特别的,但是TextBoxBase的这个看起来很有趣:

        set {
            if (value != base.Text) { // Gotcha!
                base.Text = value;
                if (IsHandleCreated) {
                    // clear the modified flag
                    SendMessage(NativeMethods.EM_SETMODIFY, 0, 0);
                }
            }
        }

So, as hvd already answered, the reason is the textbox does not raise TextChanged if old and new values are the same. I don't think the behavior will change, at least for winforms. But if you want more robust solution, here it is:

因此,正如hvd已经回答的,原因是如果旧值和新值相同,文本框不会引发TextChanged。我认为这种行为不会改变,至少对winforms来说是这样。但如果你想要更稳健的解决方案,这里是:

    private void RunOnce(ref bool flag, Action callback)
    {
        if (!flag)
        {
            try
            {
                flag = true;
                callback();
            }
            finally
            {
                flag = false;
            }
        }
    }

    private bool inMirror;
    private void realText_TextChanged(object sender, EventArgs e)
    {
        RunOnce(ref inMirror, () =>
        {
            mirrorText.Text = mirror(realText.Text);
        });
    }

    private void mirrorText_TextChanged(object sender, EventArgs e)
    {
        RunOnce(ref inMirror, () =>
        {
            realText.Text = mirror(mirrorText.Text);
        });
    }

    private string mirror(string text)
    {
        return new string(text.Reverse().ToArray()).Replace("\n\r", "\r\n");
    }

P.S. mirror() will fail on surrogate pairs. Here're some solutions.

在代理对上,P.S. mirror()将失败。下面是一些解决方案。

#4


-1  

If textbox has a Text, and we try to change it with the same Text, the TextChange event is not raising because new text is same as the previous. In your code, the realText_TextChanged event reverses the text and changes the mirrorText with it. The mirrorText_TextChanged event reverses the text and try to change the realText. The realText has already this text and does not raises the realText_TextChanged event.

如果文本框中有一个文本,我们试图用相同的文本修改它,那么TextChange事件不会发生,因为新文本与之前的文本相同。在您的代码中,realText_TextChanged事件反转文本并使用它更改mirrorText。mirrorText_TextChanged事件反转文本并尝试更改realText。realText已经有此文本,不会引发realText_TextChanged事件。